mirror of
https://github.com/meshtastic/firmware.git
synced 2026-02-03 23:52:01 +00:00
Compare commits
122 Commits
fix-ota-sc
...
sfpp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0562a674e2 | ||
|
|
9d06c1bf34 | ||
|
|
1d30342c00 | ||
|
|
7b03980e0a | ||
|
|
8f630bfcf3 | ||
|
|
7bbfe99fbe | ||
|
|
caae6bc597 | ||
|
|
200e79e800 | ||
|
|
c19fc62683 | ||
|
|
4cf01e7e53 | ||
|
|
ad4b1d9c2b | ||
|
|
5dd06edd00 | ||
|
|
eeb7373043 | ||
|
|
dbded86dcb | ||
|
|
45fbc0f9d3 | ||
|
|
61b39acc7d | ||
|
|
8af9e7fbdc | ||
|
|
1f7ed6888a | ||
|
|
31bf51b3f2 | ||
|
|
334a4f04cd | ||
|
|
338dd119fb | ||
|
|
f36406e5ef | ||
|
|
7f565fd524 | ||
|
|
5af6a48326 | ||
|
|
5633848d75 | ||
|
|
8b8c1881a8 | ||
|
|
a1d6978626 | ||
|
|
a67cf0f726 | ||
|
|
456fa3ddeb | ||
|
|
5adc9663b7 | ||
|
|
5de0654819 | ||
|
|
ab781e9f2d | ||
|
|
595b5f19b3 | ||
|
|
4bb93c1ed2 | ||
|
|
5582e94009 | ||
|
|
e33fbca8d6 | ||
|
|
aca7fe9f95 | ||
|
|
5a0644cd4f | ||
|
|
e990198628 | ||
|
|
6c69d9e74c | ||
|
|
e03f1b5c5e | ||
|
|
f46a9dfe7b | ||
|
|
9824357c50 | ||
|
|
942f2cb3d1 | ||
|
|
76beeda392 | ||
|
|
821735495a | ||
|
|
9ab2ee3483 | ||
|
|
4e92f7fa09 | ||
|
|
ae2a06eccd | ||
|
|
3ae331eb89 | ||
|
|
74a6c9f447 | ||
|
|
c77709a327 | ||
|
|
325f7d2e55 | ||
|
|
c6fc7986f1 | ||
|
|
8ecce1eb5c | ||
|
|
21c0dcaabb | ||
|
|
1b13f872db | ||
|
|
8b5141ddb7 | ||
|
|
ee25a0a0e1 | ||
|
|
436f174bce | ||
|
|
a34cd4ca6f | ||
|
|
8c37669213 | ||
|
|
8a059bae23 | ||
|
|
1869f2108d | ||
|
|
f5b41c2f2c | ||
|
|
83c8875060 | ||
|
|
9134239faa | ||
|
|
b3d1d563e9 | ||
|
|
c7f816e63f | ||
|
|
dd4fb6b0bc | ||
|
|
87798429fa | ||
|
|
78baaf4484 | ||
|
|
b3b115b6a6 | ||
|
|
b90b5ff40e | ||
|
|
b7028fff08 | ||
|
|
7d6a0f20c6 | ||
|
|
9b7384507d | ||
|
|
7d7091ef94 | ||
|
|
baccd0c532 | ||
|
|
1fecdc7603 | ||
|
|
1625fd88d7 | ||
|
|
fe22460f25 | ||
|
|
f634b7dd60 | ||
|
|
f56e651787 | ||
|
|
55af6c4726 | ||
|
|
d272b28ed4 | ||
|
|
f8c27d1714 | ||
|
|
25383c9523 | ||
|
|
6d90b6536e | ||
|
|
0759197ab3 | ||
|
|
bbfca12d50 | ||
|
|
d44c3a8e1a | ||
|
|
1cef1094a0 | ||
|
|
02d4ca2983 | ||
|
|
36e8a498f1 | ||
|
|
d63b583ea2 | ||
|
|
14073e2c9f | ||
|
|
39a6ffc664 | ||
|
|
8be790890c | ||
|
|
426a7c19dd | ||
|
|
39c0824abb | ||
|
|
a8a5086b6d | ||
|
|
428b839254 | ||
|
|
a70d350ce3 | ||
|
|
00a3249c56 | ||
|
|
b51235d4fd | ||
|
|
d07f5be548 | ||
|
|
739ad0dc31 | ||
|
|
e8fd5174ec | ||
|
|
96726d22cd | ||
|
|
3330d297b1 | ||
|
|
e9ed2c0335 | ||
|
|
3cbc5b7a8d | ||
|
|
20bf822a48 | ||
|
|
14ee1ed075 | ||
|
|
4d48d517e0 | ||
|
|
ffdb3bc393 | ||
|
|
6e83a9a0b3 | ||
|
|
73cfa3c884 | ||
|
|
f2b6383cbb | ||
|
|
28d507f043 | ||
|
|
d508de9568 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,3 +50,6 @@ idf_component.yml
|
|||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
/sdkconfig.*
|
/sdkconfig.*
|
||||||
.dummy/*
|
.dummy/*
|
||||||
|
|
||||||
|
# PYTHONPATH used by the Nix shell
|
||||||
|
.python3
|
||||||
|
|||||||
@@ -203,6 +203,16 @@ HostMetrics:
|
|||||||
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
|
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
|
||||||
|
|
||||||
|
|
||||||
|
StoreAndForward:
|
||||||
|
# Enabled: true # Enable Store and Forward++, true by default
|
||||||
|
# DBPath: /var/lib/meshtasticd/ # Path to the S&F++ Sqlite DB
|
||||||
|
# Stratum0: false # Specify if this node is a Stratum 0 node, the controller node.
|
||||||
|
# InitialSync: 10 # Number of messages to
|
||||||
|
# Hops: 3 # Number of hops to use for SF++ messages
|
||||||
|
# AnnounceInterval: 5 # Interval in minutes between announcing tip of chain hash
|
||||||
|
# MaxChainLength: 1000 # Maximum number of messages to store in a chain
|
||||||
|
|
||||||
|
|
||||||
Config:
|
Config:
|
||||||
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
|
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
|
||||||
# DisplayMode: COLOR # uncomment to force MUI
|
# DisplayMode: COLOR # uncomment to force MUI
|
||||||
|
|||||||
44
flake.lock
generated
Normal file
44
flake.lock
generated
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767039857,
|
||||||
|
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1766314097,
|
||||||
|
"narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
66
flake.nix
Normal file
66
flake.nix
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
description = "Nix flake to compile Meshtastic firmware";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
|
||||||
|
# Shim to make flake.nix work with stable Nix.
|
||||||
|
flake-compat = {
|
||||||
|
url = "github:NixOS/flake-compat";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
inputs:
|
||||||
|
let
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
|
||||||
|
forAllSystems =
|
||||||
|
fn:
|
||||||
|
lib.genAttrs lib.systems.flakeExposed (
|
||||||
|
system:
|
||||||
|
fn {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
inherit system;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forAllSystems (
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
python3 = pkgs.python312.withPackages (
|
||||||
|
ps: with ps; [
|
||||||
|
google
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
python3
|
||||||
|
platformio
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
# Set up PlatformIO to use a local core directory.
|
||||||
|
export PLATFORMIO_CORE_DIR=$PWD/.platformio
|
||||||
|
# Tell pip to put packages into $PIP_PREFIX instead of the usual
|
||||||
|
# location. This is especially necessary under NixOS to avoid having
|
||||||
|
# pip trying to write to the read-only Nix store. For more info,
|
||||||
|
# see https://wiki.nixos.org/wiki/Python
|
||||||
|
export PIP_PREFIX=$PWD/.python3
|
||||||
|
export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}"
|
||||||
|
export PATH="$PIP_PREFIX/bin:$PATH"
|
||||||
|
# Avoids reproducibility issues with some Python packages
|
||||||
|
# See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl
|
||||||
|
unset SOURCE_DATE_EPOCH
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -96,7 +96,7 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
||||||
end2endzone/NonBlockingRTTTL@1.4.0
|
end2endzone/NonBlockingRTTTL@1.4.0
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
|
||||||
|
|
||||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||||
[networking_base]
|
[networking_base]
|
||||||
|
|||||||
12
shell.nix
Normal file
12
shell.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
(import (
|
||||||
|
let
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
nodeName = lock.nodes.root.inputs.flake-compat;
|
||||||
|
in
|
||||||
|
fetchTarball {
|
||||||
|
url =
|
||||||
|
lock.nodes.${nodeName}.locked.url
|
||||||
|
or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
|
||||||
|
sha256 = lock.nodes.${nodeName}.locked.narHash;
|
||||||
|
}
|
||||||
|
) { src = ./.; }).shellNix
|
||||||
@@ -89,22 +89,14 @@ class BluetoothStatus : public Status
|
|||||||
case ConnectionState::CONNECTED:
|
case ConnectionState::CONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
#ifdef BLE_LED_INVERTED
|
digitalWrite(BLE_LED, LED_STATE_ON);
|
||||||
digitalWrite(BLE_LED, LOW);
|
|
||||||
#else
|
|
||||||
digitalWrite(BLE_LED, HIGH);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConnectionState::DISCONNECTED:
|
case ConnectionState::DISCONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
#ifdef BLE_LED_INVERTED
|
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||||
digitalWrite(BLE_LED, HIGH);
|
|
||||||
#else
|
|
||||||
digitalWrite(BLE_LED, LOW);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -816,6 +816,9 @@ void Power::shutdown()
|
|||||||
#endif
|
#endif
|
||||||
#ifdef PIN_LED3
|
#ifdef PIN_LED3
|
||||||
ledOff(PIN_LED3);
|
ledOff(PIN_LED3);
|
||||||
|
#endif
|
||||||
|
#ifdef LED_NOTIFICATION
|
||||||
|
ledOff(LED_NOTIFICATION);
|
||||||
#endif
|
#endif
|
||||||
doDeepSleep(DELAY_FOREVER, true, true);
|
doDeepSleep(DELAY_FOREVER, true, true);
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
|
|||||||
@@ -390,9 +390,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#ifndef HAS_RADIO
|
#ifndef HAS_RADIO
|
||||||
#define HAS_RADIO 0
|
#define HAS_RADIO 0
|
||||||
#endif
|
#endif
|
||||||
#ifndef HAS_RTC
|
|
||||||
#define HAS_RTC 0
|
|
||||||
#endif
|
|
||||||
#ifndef HAS_CPU_SHUTDOWN
|
#ifndef HAS_CPU_SHUTDOWN
|
||||||
#define HAS_CPU_SHUTDOWN 0
|
#define HAS_CPU_SHUTDOWN 0
|
||||||
#endif
|
#endif
|
||||||
@@ -428,12 +425,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define HAS_RGB_LED
|
#define HAS_RGB_LED
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef LED_STATE_OFF
|
|
||||||
#define LED_STATE_OFF 0
|
|
||||||
#endif
|
|
||||||
#ifndef LED_STATE_ON
|
#ifndef LED_STATE_ON
|
||||||
#define LED_STATE_ON 1
|
#define LED_STATE_ON 1
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef LED_STATE_OFF
|
||||||
|
#define LED_STATE_OFF (LED_STATE_ON ^ 1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ledOff
|
||||||
|
#define ledOff(pin) pinMode(pin, INPUT)
|
||||||
|
#endif
|
||||||
|
|
||||||
// default mapping of pins
|
// default mapping of pins
|
||||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||||
|
|||||||
@@ -276,11 +276,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
settimeofday(tv, NULL);
|
settimeofday(tv, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// nrf52 doesn't have a readable RTC (yet - software not written)
|
|
||||||
#if HAS_RTC
|
|
||||||
readFromRTC();
|
readFromRTC();
|
||||||
#endif
|
|
||||||
|
|
||||||
return RTCSetResultSuccess;
|
return RTCSetResultSuccess;
|
||||||
} else {
|
} else {
|
||||||
return RTCSetResultNotSet; // RTC was already set with a higher quality time
|
return RTCSetResultNotSet; // RTC was already set with a higher quality time
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ InkHUD::Tile *InkHUD::Applet::getTile()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the applet
|
// Draw the applet
|
||||||
void InkHUD::Applet::render()
|
void InkHUD::Applet::render(bool full)
|
||||||
{
|
{
|
||||||
assert(assignedTile); // Ensure that we have a tile
|
assert(assignedTile); // Ensure that we have a tile
|
||||||
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
||||||
@@ -65,10 +65,11 @@ void InkHUD::Applet::render()
|
|||||||
wantRender = false; // Flag set by requestUpdate
|
wantRender = false; // Flag set by requestUpdate
|
||||||
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
|
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
|
||||||
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
|
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
|
||||||
|
wantFullRender = true; // Default to a full render
|
||||||
|
|
||||||
updateDimensions();
|
updateDimensions();
|
||||||
resetDrawingSpace();
|
resetDrawingSpace();
|
||||||
onRender(); // Derived applet's drawing takes place here
|
onRender(full); // Draw the applet
|
||||||
|
|
||||||
// Handle "Tile Highlighting"
|
// Handle "Tile Highlighting"
|
||||||
// Some devices may use an auxiliary button to switch between tiles
|
// Some devices may use an auxiliary button to switch between tiles
|
||||||
@@ -115,6 +116,11 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
|
|||||||
return wantUpdateType;
|
return wantUpdateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InkHUD::Applet::wantsFullRender()
|
||||||
|
{
|
||||||
|
return wantFullRender;
|
||||||
|
}
|
||||||
|
|
||||||
// Get size of the applet's drawing space from its tile
|
// Get size of the applet's drawing space from its tile
|
||||||
// Performed immediately before derived applet's drawing code runs
|
// Performed immediately before derived applet's drawing code runs
|
||||||
void InkHUD::Applet::updateDimensions()
|
void InkHUD::Applet::updateDimensions()
|
||||||
@@ -142,10 +148,11 @@ void InkHUD::Applet::resetDrawingSpace()
|
|||||||
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
||||||
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
|
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
|
||||||
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
||||||
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
|
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full)
|
||||||
{
|
{
|
||||||
wantRender = true;
|
wantRender = true;
|
||||||
wantUpdateType = type;
|
wantUpdateType = type;
|
||||||
|
wantFullRender = full;
|
||||||
inkhud->requestUpdate();
|
inkhud->requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ class Applet : public GFX
|
|||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
|
|
||||||
void render(); // Draw the applet
|
void render(bool full); // Draw the applet
|
||||||
bool wantsToRender(); // Check whether applet wants to render
|
bool wantsToRender(); // Check whether applet wants to render
|
||||||
bool wantsToAutoshow(); // Check whether applet wants to become foreground
|
bool wantsToAutoshow(); // Check whether applet wants to become foreground
|
||||||
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
||||||
|
bool wantsFullRender(); // Check whether applet wants to render over its previous render
|
||||||
void updateDimensions(); // Get current size from tile
|
void updateDimensions(); // Get current size from tile
|
||||||
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ class Applet : public GFX
|
|||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
|
|
||||||
virtual void onRender() = 0; // All drawing happens here
|
virtual void onRender(bool full) = 0; // For drawing the applet
|
||||||
virtual void onActivate() {}
|
virtual void onActivate() {}
|
||||||
virtual void onDeactivate() {}
|
virtual void onDeactivate() {}
|
||||||
virtual void onForeground() {}
|
virtual void onForeground() {}
|
||||||
@@ -96,6 +97,9 @@ class Applet : public GFX
|
|||||||
virtual void onNavDown() {}
|
virtual void onNavDown() {}
|
||||||
virtual void onNavLeft() {}
|
virtual void onNavLeft() {}
|
||||||
virtual void onNavRight() {}
|
virtual void onNavRight() {}
|
||||||
|
virtual void onFreeText(char c) {}
|
||||||
|
virtual void onFreeTextDone() {}
|
||||||
|
virtual void onFreeTextCancel() {}
|
||||||
|
|
||||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||||
|
|
||||||
@@ -108,8 +112,9 @@ class Applet : public GFX
|
|||||||
protected:
|
protected:
|
||||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
||||||
|
|
||||||
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
|
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED,
|
||||||
void requestAutoshow(); // Ask for applet to be moved to foreground
|
bool full = true); // Ask WindowManager to schedule a display update
|
||||||
|
void requestAutoshow(); // Ask for applet to be moved to foreground
|
||||||
|
|
||||||
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
||||||
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
||||||
@@ -164,6 +169,7 @@ class Applet : public GFX
|
|||||||
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
|
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
|
||||||
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
|
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
|
||||||
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
|
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
|
||||||
|
bool wantFullRender = true; // Render with a fresh canvas
|
||||||
|
|
||||||
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
|
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
|
||||||
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
|
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::MapApplet::onRender()
|
void InkHUD::MapApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Abort if no markers to render
|
// Abort if no markers to render
|
||||||
if (!enoughMarkers()) {
|
if (!enoughMarkers()) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
class MapApplet : public Applet
|
class MapApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
||||||
void InkHUD::NodeListApplet::onRender()
|
void InkHUD::NodeListApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule
|
|||||||
public:
|
public:
|
||||||
NodeListApplet(const char *name);
|
NodeListApplet(const char *name);
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
// Our basic example doesn't do anything useful. It just passively prints some text.
|
// Our basic example doesn't do anything useful. It just passively prints some text.
|
||||||
void InkHUD::BasicExampleApplet::onRender()
|
void InkHUD::BasicExampleApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Hello, World!");
|
printAt(0, 0, "Hello, World!");
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet
|
|||||||
// You must have an onRender() method
|
// You must have an onRender() method
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
|
|||||||
// We can trigger a render by calling requestUpdate()
|
// We can trigger a render by calling requestUpdate()
|
||||||
// Render might be called by some external source
|
// Render might be called by some external source
|
||||||
// We should always be ready to draw
|
// We should always be ready to draw
|
||||||
void InkHUD::NewMsgExampleApplet::onRender()
|
void InkHUD::NewMsgExampleApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
|
|||||||
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
|
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
|
||||||
|
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
// Your applet might also want to use some of these
|
// Your applet might also want to use some of these
|
||||||
// Useful for setting up or tidying up
|
// Useful for setting up or tidying up
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet()
|
|||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onRender()
|
void InkHUD::AlignStickApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
printAt(0, 0, "Align Joystick:");
|
printAt(0, 0, "Align Joystick:");
|
||||||
@@ -152,19 +152,17 @@ void InkHUD::AlignStickApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onButtonLongPress()
|
void InkHUD::AlignStickApplet::onButtonLongPress()
|
||||||
{
|
{
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onExitLong()
|
void InkHUD::AlignStickApplet::onExitLong()
|
||||||
{
|
{
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavUp()
|
void InkHUD::AlignStickApplet::onNavUp()
|
||||||
@@ -172,7 +170,6 @@ void InkHUD::AlignStickApplet::onNavUp()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavDown()
|
void InkHUD::AlignStickApplet::onNavDown()
|
||||||
@@ -181,7 +178,6 @@ void InkHUD::AlignStickApplet::onNavDown()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavLeft()
|
void InkHUD::AlignStickApplet::onNavLeft()
|
||||||
@@ -190,7 +186,6 @@ void InkHUD::AlignStickApplet::onNavLeft()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavRight()
|
void InkHUD::AlignStickApplet::onNavRight()
|
||||||
@@ -199,7 +194,6 @@ void InkHUD::AlignStickApplet::onNavRight()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
AlignStickApplet();
|
AlignStickApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonLongPress() override;
|
void onButtonLongPress() override;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
||||||
{
|
{
|
||||||
|
alwaysRender = true; // render everytime the screen is updated
|
||||||
|
|
||||||
// Show at boot, if user has previously enabled the feature
|
// Show at boot, if user has previously enabled the feature
|
||||||
if (settings->optionalFeatures.batteryIcon)
|
if (settings->optionalFeatures.batteryIcon)
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
@@ -44,7 +46,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
|||||||
return 0; // Tell Observable to continue informing other observers
|
return 0; // Tell Observable to continue informing other observers
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::BatteryIconApplet::onRender()
|
void InkHUD::BatteryIconApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Fill entire tile
|
// Fill entire tile
|
||||||
// - size of icon controlled by size of tile
|
// - size of icon controlled by size of tile
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
BatteryIconApplet();
|
BatteryIconApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||||
|
#include "./KeyboardApplet.h"
|
||||||
|
|
||||||
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
|
InkHUD::KeyboardApplet::KeyboardApplet()
|
||||||
|
{
|
||||||
|
// Calculate row widths
|
||||||
|
for (uint8_t row = 0; row < KBD_ROWS; row++) {
|
||||||
|
rowWidths[row] = 0;
|
||||||
|
for (uint8_t col = 0; col < KBD_COLS; col++)
|
||||||
|
rowWidths[row] += keyWidths[row * KBD_COLS + col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onRender(bool full)
|
||||||
|
{
|
||||||
|
uint16_t em = fontSmall.lineHeight(); // 16 pt
|
||||||
|
uint16_t keyH = Y(1.0) / KBD_ROWS;
|
||||||
|
int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2;
|
||||||
|
|
||||||
|
if (full) { // Draw full keyboard
|
||||||
|
for (uint8_t row = 0; row < KBD_ROWS; row++) {
|
||||||
|
|
||||||
|
// Calculate the remaining space to be used as padding
|
||||||
|
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
||||||
|
|
||||||
|
// Draw keys
|
||||||
|
uint16_t xPos = 0;
|
||||||
|
for (uint8_t col = 0; col < KBD_COLS; col++) {
|
||||||
|
Color fgcolor = BLACK;
|
||||||
|
uint8_t index = row * KBD_COLS + col;
|
||||||
|
uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1));
|
||||||
|
uint16_t keyY = row * keyH;
|
||||||
|
uint16_t keyW = (keyWidths[index] * em) >> 4;
|
||||||
|
if (index == selectedKey) {
|
||||||
|
fgcolor = WHITE;
|
||||||
|
fillRect(keyX, keyY, keyW, keyH, BLACK);
|
||||||
|
}
|
||||||
|
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor);
|
||||||
|
xPos += keyWidths[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Only draw the difference
|
||||||
|
if (selectedKey != prevSelectedKey) {
|
||||||
|
// Draw previously selected key
|
||||||
|
uint8_t row = prevSelectedKey / KBD_COLS;
|
||||||
|
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
||||||
|
uint16_t xPos = 0;
|
||||||
|
for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++)
|
||||||
|
xPos += keyWidths[i];
|
||||||
|
uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
|
||||||
|
uint16_t keyY = row * keyH;
|
||||||
|
uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4;
|
||||||
|
fillRect(keyX, keyY, keyW, keyH, WHITE);
|
||||||
|
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK);
|
||||||
|
|
||||||
|
// Draw newly selected key
|
||||||
|
row = selectedKey / KBD_COLS;
|
||||||
|
keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
||||||
|
xPos = 0;
|
||||||
|
for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++)
|
||||||
|
xPos += keyWidths[i];
|
||||||
|
keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
|
||||||
|
keyY = row * keyH;
|
||||||
|
keyW = (keyWidths[selectedKey] * em) >> 4;
|
||||||
|
fillRect(keyX, keyY, keyW, keyH, BLACK);
|
||||||
|
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSelectedKey = selectedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the key label corresponding to the char
|
||||||
|
// for most keys it draws the character itself
|
||||||
|
// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs
|
||||||
|
void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color)
|
||||||
|
{
|
||||||
|
if (key == '\b') {
|
||||||
|
// Draw backspace glyph: 13 x 9 px
|
||||||
|
/**
|
||||||
|
* [][][][][][][][][]
|
||||||
|
* [][] []
|
||||||
|
* [][] [] [] []
|
||||||
|
* [][] [] [] []
|
||||||
|
* [][] [] []
|
||||||
|
* [][] [] [] []
|
||||||
|
* [][] [] [] []
|
||||||
|
* [][] []
|
||||||
|
* [][][][][][][][][]
|
||||||
|
*/
|
||||||
|
const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0,
|
||||||
|
0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8};
|
||||||
|
uint16_t leftPadding = (width - 13) >> 1;
|
||||||
|
drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color);
|
||||||
|
} else if (key == '\n') {
|
||||||
|
// Draw done glyph: 12 x 9 px
|
||||||
|
/**
|
||||||
|
* [][]
|
||||||
|
* [][]
|
||||||
|
* [][]
|
||||||
|
* [][]
|
||||||
|
* [][]
|
||||||
|
* [][] [][]
|
||||||
|
* [][] [][]
|
||||||
|
* [][][]
|
||||||
|
* []
|
||||||
|
*/
|
||||||
|
const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03,
|
||||||
|
0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00};
|
||||||
|
uint16_t leftPadding = (width - 12) >> 1;
|
||||||
|
drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color);
|
||||||
|
} else if (key == ' ') {
|
||||||
|
// Draw space glyph: 13 x 9 px
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* [] []
|
||||||
|
* [] []
|
||||||
|
* [][][][][][][][][][][][][]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||||
|
0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
uint16_t leftPadding = (width - 13) >> 1;
|
||||||
|
drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color);
|
||||||
|
} else if (key == '\x1b') {
|
||||||
|
setTextColor(color);
|
||||||
|
std::string keyText = "ESC";
|
||||||
|
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
|
||||||
|
printAt(left + leftPadding, top, keyText);
|
||||||
|
} else {
|
||||||
|
setTextColor(color);
|
||||||
|
if (key >= 0x61)
|
||||||
|
key -= 32; // capitalize
|
||||||
|
std::string keyText = std::string(1, key);
|
||||||
|
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
|
||||||
|
printAt(left + leftPadding, top, keyText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onForeground()
|
||||||
|
{
|
||||||
|
handleInput = true; // Intercept the button input for our applet
|
||||||
|
|
||||||
|
// Select the first key
|
||||||
|
selectedKey = 0;
|
||||||
|
prevSelectedKey = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onBackground()
|
||||||
|
{
|
||||||
|
handleInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onButtonShortPress()
|
||||||
|
{
|
||||||
|
char key = keys[selectedKey];
|
||||||
|
if (key == '\n') {
|
||||||
|
inkhud->freeTextDone();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
} else if (key == '\x1b') {
|
||||||
|
inkhud->freeTextCancel();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
} else {
|
||||||
|
inkhud->freeText(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onButtonLongPress()
|
||||||
|
{
|
||||||
|
char key = keys[selectedKey];
|
||||||
|
if (key == '\n') {
|
||||||
|
inkhud->freeTextDone();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
} else if (key == '\x1b') {
|
||||||
|
inkhud->freeTextCancel();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
} else {
|
||||||
|
if (key >= 0x61)
|
||||||
|
key -= 32; // capitalize
|
||||||
|
inkhud->freeText(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onExitShort()
|
||||||
|
{
|
||||||
|
inkhud->freeTextCancel();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onExitLong()
|
||||||
|
{
|
||||||
|
inkhud->freeTextCancel();
|
||||||
|
inkhud->closeKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onNavUp()
|
||||||
|
{
|
||||||
|
if (selectedKey < KBD_COLS) // wrap
|
||||||
|
selectedKey += KBD_COLS * (KBD_ROWS - 1);
|
||||||
|
else // move 1 row back
|
||||||
|
selectedKey -= KBD_COLS;
|
||||||
|
|
||||||
|
// Request rendering over the previously drawn render
|
||||||
|
requestUpdate(EInk::UpdateTypes::FAST, false);
|
||||||
|
// Force an update to bypass lockRequests
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onNavDown()
|
||||||
|
{
|
||||||
|
selectedKey += KBD_COLS;
|
||||||
|
selectedKey %= (KBD_COLS * KBD_ROWS);
|
||||||
|
|
||||||
|
// Request rendering over the previously drawn render
|
||||||
|
requestUpdate(EInk::UpdateTypes::FAST, false);
|
||||||
|
// Force an update to bypass lockRequests
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onNavLeft()
|
||||||
|
{
|
||||||
|
if (selectedKey % KBD_COLS == 0) // wrap
|
||||||
|
selectedKey += KBD_COLS - 1;
|
||||||
|
else // move 1 column back
|
||||||
|
selectedKey--;
|
||||||
|
|
||||||
|
// Request rendering over the previously drawn render
|
||||||
|
requestUpdate(EInk::UpdateTypes::FAST, false);
|
||||||
|
// Force an update to bypass lockRequests
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::KeyboardApplet::onNavRight()
|
||||||
|
{
|
||||||
|
if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap
|
||||||
|
selectedKey -= KBD_COLS - 1;
|
||||||
|
else // move 1 column forward
|
||||||
|
selectedKey++;
|
||||||
|
|
||||||
|
// Request rendering over the previously drawn render
|
||||||
|
requestUpdate(EInk::UpdateTypes::FAST, false);
|
||||||
|
// Force an update to bypass lockRequests
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t InkHUD::KeyboardApplet::getKeyboardHeight()
|
||||||
|
{
|
||||||
|
const uint16_t keyH = fontSmall.lineHeight() * 1.2;
|
||||||
|
return keyH * KBD_ROWS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
System Applet to render an on-screeen keyboard
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||||
|
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||||
|
#include <string>
|
||||||
|
namespace NicheGraphics::InkHUD
|
||||||
|
{
|
||||||
|
|
||||||
|
class KeyboardApplet : public SystemApplet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KeyboardApplet();
|
||||||
|
|
||||||
|
void onRender(bool full) override;
|
||||||
|
void onForeground() override;
|
||||||
|
void onBackground() 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;
|
||||||
|
|
||||||
|
static uint16_t getKeyboardHeight(); // used to set the keyboard tile height
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color);
|
||||||
|
|
||||||
|
static const uint8_t KBD_COLS = 11;
|
||||||
|
static const uint8_t KBD_ROWS = 4;
|
||||||
|
|
||||||
|
const char keys[KBD_COLS * KBD_ROWS] = {
|
||||||
|
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0
|
||||||
|
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1
|
||||||
|
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2
|
||||||
|
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// This array represents the widths of each key in points
|
||||||
|
// 16 pt = line height of the text
|
||||||
|
const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = {
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t rowWidths[KBD_ROWS];
|
||||||
|
uint8_t selectedKey = 0; // selected key index
|
||||||
|
uint8_t prevSelectedKey = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
|||||||
// This is then drawn with a FULL refresh by Renderer::begin
|
// This is then drawn with a FULL refresh by Renderer::begin
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onRender()
|
void InkHUD::LogoApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Size of the region which the logo should "scale to fit"
|
// Size of the region which the logo should "scale to fit"
|
||||||
uint16_t logoWLimit = X(0.8);
|
uint16_t logoWLimit = X(0.8);
|
||||||
@@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin displaying the screen which is shown at shutdown
|
// Begin displaying the screen which is shown at shutdown
|
||||||
@@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
// Intention is to restore display health.
|
// Intention is to restore display health.
|
||||||
|
|
||||||
inverted = true;
|
inverted = true;
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
||||||
delay(1000); // Cooldown. Back to back updates aren't great for health.
|
delay(1000); // Cooldown. Back to back updates aren't great for health.
|
||||||
inverted = false;
|
inverted = false;
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
||||||
delay(1000); // Cooldown
|
delay(1000); // Cooldown
|
||||||
|
|
||||||
// Prepare for the powered-off screen now
|
// Prepare for the powered-off screen now
|
||||||
@@ -176,7 +176,7 @@ void InkHUD::LogoApplet::onReboot()
|
|||||||
textTitle = "Rebooting...";
|
textTitle = "Rebooting...";
|
||||||
fontTitle = fontSmall;
|
fontTitle = fontSmall;
|
||||||
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
||||||
// Perform the update right now, waiting here until complete
|
// Perform the update right now, waiting here until complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogoApplet();
|
LogoApplet();
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onShutdown() override;
|
void onShutdown() override;
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ namespace NicheGraphics::InkHUD
|
|||||||
enum MenuAction {
|
enum MenuAction {
|
||||||
NO_ACTION,
|
NO_ACTION,
|
||||||
SEND_PING,
|
SEND_PING,
|
||||||
|
FREE_TEXT,
|
||||||
STORE_CANNEDMESSAGE_SELECTION,
|
STORE_CANNEDMESSAGE_SELECTION,
|
||||||
SEND_CANNEDMESSAGE,
|
SEND_CANNEDMESSAGE,
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
BACK,
|
|
||||||
NEXT_TILE,
|
NEXT_TILE,
|
||||||
TOGGLE_BACKLIGHT,
|
TOGGLE_BACKLIGHT,
|
||||||
TOGGLE_GPS,
|
TOGGLE_GPS,
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ void InkHUD::MenuApplet::onForeground()
|
|||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
OSThread::enabled = true;
|
OSThread::enabled = true;
|
||||||
|
|
||||||
|
freeTextMode = false;
|
||||||
|
|
||||||
// Upgrade the refresh to FAST, for guaranteed responsiveness
|
// Upgrade the refresh to FAST, for guaranteed responsiveness
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,8 @@ void InkHUD::MenuApplet::onBackground()
|
|||||||
SystemApplet::lockRequests = false;
|
SystemApplet::lockRequests = false;
|
||||||
SystemApplet::handleInput = false;
|
SystemApplet::handleInput = false;
|
||||||
|
|
||||||
|
handleFreeText = false;
|
||||||
|
|
||||||
// Restore the user applet whose tile we borrowed
|
// Restore the user applet whose tile we borrowed
|
||||||
if (borrowedTileOwner)
|
if (borrowedTileOwner)
|
||||||
borrowedTileOwner->bringToForeground();
|
borrowedTileOwner->bringToForeground();
|
||||||
@@ -325,10 +329,6 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BACK:
|
|
||||||
showPage(item.nextPage);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NEXT_TILE:
|
case NEXT_TILE:
|
||||||
inkhud->nextTile();
|
inkhud->nextTile();
|
||||||
// Unselect menu item after tile change
|
// Unselect menu item after tile change
|
||||||
@@ -344,12 +344,26 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FREE_TEXT:
|
||||||
|
OSThread::enabled = false;
|
||||||
|
handleFreeText = true;
|
||||||
|
cm.freeTextItem.rawText.erase(); // clear the previous freetext message
|
||||||
|
freeTextMode = true; // render input field instead of normal menu
|
||||||
|
// Open the on-screen keyboard if the joystick is enabled
|
||||||
|
if (settings->joystick.enabled)
|
||||||
|
inkhud->openKeyboard();
|
||||||
|
break;
|
||||||
|
|
||||||
case STORE_CANNEDMESSAGE_SELECTION:
|
case STORE_CANNEDMESSAGE_SELECTION:
|
||||||
cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry
|
if (!settings->joystick.enabled)
|
||||||
|
cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry
|
||||||
|
else
|
||||||
|
cm.selectedMessageItem = &cm.messageItems.at(cursor - 2); // Minus two: offset for the "Send Ping" and free text entry
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SEND_CANNEDMESSAGE:
|
case SEND_CANNEDMESSAGE:
|
||||||
cm.selectedRecipientItem = &cm.recipientItems.at(cursor);
|
cm.selectedRecipientItem = &cm.recipientItems.at(cursor);
|
||||||
|
// send selected message
|
||||||
sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str());
|
sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str());
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here
|
||||||
break;
|
break;
|
||||||
@@ -868,6 +882,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
|
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case ROOT:
|
case ROOT:
|
||||||
|
previousPage = MenuPage::EXIT;
|
||||||
// Optional: next applet
|
// Optional: next applet
|
||||||
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1)
|
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1)
|
||||||
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
|
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
|
||||||
@@ -878,7 +893,6 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG));
|
items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG));
|
||||||
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::EXIT;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SEND:
|
case SEND:
|
||||||
@@ -888,11 +902,12 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
|
|
||||||
case CANNEDMESSAGE_RECIPIENT:
|
case CANNEDMESSAGE_RECIPIENT:
|
||||||
populateRecipientPage();
|
populateRecipientPage();
|
||||||
previousPage = MenuPage::OPTIONS;
|
previousPage = MenuPage::SEND;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPTIONS:
|
case OPTIONS:
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT));
|
previousPage = MenuPage::ROOT;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
// Optional: backlight
|
// Optional: backlight
|
||||||
if (settings->optionalMenuItems.backlight)
|
if (settings->optionalMenuItems.backlight)
|
||||||
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
||||||
@@ -916,31 +931,32 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
||||||
items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors));
|
items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::ROOT;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case APPLETS:
|
case APPLETS:
|
||||||
populateAppletPage(); // must be first
|
|
||||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
|
||||||
previousPage = MenuPage::OPTIONS;
|
previousPage = MenuPage::OPTIONS;
|
||||||
|
populateAppletPage(); // must be first
|
||||||
|
items.insert(items.begin(), MenuItem("Back", previousPage));
|
||||||
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUTOSHOW:
|
case AUTOSHOW:
|
||||||
populateAutoshowPage(); // must be first
|
|
||||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
|
||||||
previousPage = MenuPage::OPTIONS;
|
previousPage = MenuPage::OPTIONS;
|
||||||
|
populateAutoshowPage(); // must be first
|
||||||
|
items.insert(items.begin(), MenuItem("Back", previousPage));
|
||||||
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RECENTS:
|
case RECENTS:
|
||||||
|
previousPage = MenuPage::OPTIONS;
|
||||||
populateRecentsPage(); // builds only the options
|
populateRecentsPage(); // builds only the options
|
||||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
items.insert(items.begin(), MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NODE_CONFIG:
|
case NODE_CONFIG:
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT));
|
previousPage = MenuPage::ROOT;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
// Radio Config Section
|
// Radio Config Section
|
||||||
items.push_back(MenuItem::Header("Radio Config"));
|
items.push_back(MenuItem::Header("Radio Config"));
|
||||||
items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA));
|
items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA));
|
||||||
@@ -965,8 +981,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NODE_CONFIG_DEVICE: {
|
case NODE_CONFIG_DEVICE: {
|
||||||
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
const char *role = DisplayFormatters::getDeviceRole(config.device.role);
|
const char *role = DisplayFormatters::getDeviceRole(config.device.role);
|
||||||
nodeConfigLabels.emplace_back("Role: " + std::string(role));
|
nodeConfigLabels.emplace_back("Role: " + std::string(role));
|
||||||
@@ -981,7 +997,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_POSITION: {
|
case NODE_CONFIG_POSITION: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS
|
||||||
const auto mode = config.position.gps_mode;
|
const auto mode = config.position.gps_mode;
|
||||||
if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||||
@@ -996,7 +1013,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_POWER: {
|
case NODE_CONFIG_POWER: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
#if defined(ARCH_ESP32)
|
#if defined(ARCH_ESP32)
|
||||||
items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving));
|
items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving));
|
||||||
#endif
|
#endif
|
||||||
@@ -1029,7 +1047,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_POWER_ADC_CAL: {
|
case NODE_CONFIG_POWER_ADC_CAL: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_POWER));
|
previousPage = MenuPage::NODE_CONFIG_POWER;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
// Instruction text (header-style, non-selectable)
|
// Instruction text (header-style, non-selectable)
|
||||||
items.push_back(MenuItem::Header("Run on full charge Only"));
|
items.push_back(MenuItem::Header("Run on full charge Only"));
|
||||||
@@ -1042,7 +1061,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_NETWORK: {
|
case NODE_CONFIG_NETWORK: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off";
|
const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off";
|
||||||
|
|
||||||
@@ -1099,7 +1119,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_DISPLAY: {
|
case NODE_CONFIG_DISPLAY: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY,
|
items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY,
|
||||||
&config.display.use_12h_clock));
|
&config.display.use_12h_clock));
|
||||||
@@ -1114,7 +1135,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_BLUETOOTH: {
|
case NODE_CONFIG_BLUETOOTH: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off";
|
const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off";
|
||||||
items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT));
|
items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT));
|
||||||
@@ -1127,8 +1149,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_LORA: {
|
case NODE_CONFIG_LORA: {
|
||||||
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
const char *region = myRegion ? myRegion->name : "Unset";
|
const char *region = myRegion ? myRegion->name : "Unset";
|
||||||
nodeConfigLabels.emplace_back("Region: " + std::string(region));
|
nodeConfigLabels.emplace_back("Region: " + std::string(region));
|
||||||
@@ -1150,7 +1172,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_CHANNELS: {
|
case NODE_CONFIG_CHANNELS: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
||||||
meshtastic_Channel &ch = channels.getByIndex(i);
|
meshtastic_Channel &ch = channels.getByIndex(i);
|
||||||
@@ -1181,7 +1204,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_CHANNEL_DETAIL: {
|
case NODE_CONFIG_CHANNEL_DETAIL: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS));
|
previousPage = MenuPage::NODE_CONFIG_CHANNELS;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
|
|
||||||
meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
||||||
|
|
||||||
@@ -1226,7 +1250,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_CHANNEL_PRECISION: {
|
case NODE_CONFIG_CHANNEL_PRECISION: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
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) {
|
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));
|
items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||||
@@ -1247,7 +1272,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NODE_CONFIG_DEVICE_ROLE: {
|
case NODE_CONFIG_DEVICE_ROLE: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE));
|
previousPage = MenuPage::NODE_CONFIG_DEVICE;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT));
|
items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT));
|
items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT));
|
items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT));
|
||||||
@@ -1257,7 +1283,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TIMEZONE:
|
case TIMEZONE:
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE));
|
previousPage = MenuPage::NODE_CONFIG_DEVICE;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE));
|
items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE));
|
||||||
items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE));
|
items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE));
|
||||||
items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE));
|
items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE));
|
||||||
@@ -1279,7 +1306,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case REGION:
|
case REGION:
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA));
|
previousPage = MenuPage::NODE_CONFIG_LORA;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT));
|
items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT));
|
items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT));
|
items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT));
|
||||||
@@ -1310,7 +1338,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NODE_CONFIG_PRESET: {
|
case NODE_CONFIG_PRESET: {
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA));
|
previousPage = MenuPage::NODE_CONFIG_LORA;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT));
|
items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT));
|
items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT));
|
items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT));
|
||||||
@@ -1323,7 +1352,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
}
|
}
|
||||||
// Administration Section
|
// Administration Section
|
||||||
case NODE_CONFIG_ADMIN_RESET:
|
case NODE_CONFIG_ADMIN_RESET:
|
||||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
previousPage = MenuPage::NODE_CONFIG;
|
||||||
|
items.push_back(MenuItem("Back", previousPage));
|
||||||
items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT));
|
items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT));
|
items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
@@ -1361,8 +1391,14 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
currentPage = page;
|
currentPage = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onRender()
|
void InkHUD::MenuApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
|
// Free text mode draws a text input field and skips the normal rendering
|
||||||
|
if (freeTextMode) {
|
||||||
|
drawInputField(0, fontSmall.lineHeight(), X(1.0), Y(1.0) - fontSmall.lineHeight() - 1, cm.freeTextItem.rawText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (items.size() == 0)
|
if (items.size() == 0)
|
||||||
LOG_ERROR("Empty Menu");
|
LOG_ERROR("Empty Menu");
|
||||||
|
|
||||||
@@ -1481,44 +1517,48 @@ void InkHUD::MenuApplet::onRender()
|
|||||||
|
|
||||||
void InkHUD::MenuApplet::onButtonShortPress()
|
void InkHUD::MenuApplet::onButtonShortPress()
|
||||||
{
|
{
|
||||||
// Push the auto-close timer back
|
if (!freeTextMode) {
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
// Push the auto-close timer back
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
if (!settings->joystick.enabled) {
|
if (!settings->joystick.enabled) {
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
cursor = (cursor + 1) % items.size();
|
cursor = (cursor + 1) % items.size();
|
||||||
} while (items.at(cursor).isHeader);
|
} while (items.at(cursor).isHeader);
|
||||||
}
|
}
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
} else {
|
|
||||||
if (cursorShown)
|
|
||||||
execute(items.at(cursor));
|
|
||||||
else
|
|
||||||
showPage(MenuPage::EXIT);
|
|
||||||
if (!wantsToRender())
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
} else {
|
||||||
|
if (cursorShown)
|
||||||
|
execute(items.at(cursor));
|
||||||
|
else
|
||||||
|
showPage(MenuPage::EXIT);
|
||||||
|
if (!wantsToRender())
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onButtonLongPress()
|
void InkHUD::MenuApplet::onButtonLongPress()
|
||||||
{
|
{
|
||||||
// Push the auto-close timer back
|
if (!freeTextMode) {
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
// Push the auto-close timer back
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
if (cursorShown)
|
if (cursorShown)
|
||||||
execute(items.at(cursor));
|
execute(items.at(cursor));
|
||||||
else
|
else
|
||||||
showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close
|
showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close
|
||||||
|
|
||||||
// If we didn't already request a specialized update, when handling a menu action,
|
// If we didn't already request a specialized update, when handling a menu action,
|
||||||
// then perform the usual fast update.
|
// then perform the usual fast update.
|
||||||
// FAST keeps things responsive: important because we're dealing with user input
|
// FAST keeps things responsive: important because we're dealing with user input
|
||||||
if (!wantsToRender())
|
if (!wantsToRender())
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onExitShort()
|
void InkHUD::MenuApplet::onExitShort()
|
||||||
@@ -1531,56 +1571,107 @@ void InkHUD::MenuApplet::onExitShort()
|
|||||||
|
|
||||||
void InkHUD::MenuApplet::onNavUp()
|
void InkHUD::MenuApplet::onNavUp()
|
||||||
{
|
{
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
if (!freeTextMode) {
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
if (cursor == 0)
|
if (cursor == 0)
|
||||||
cursor = items.size() - 1;
|
cursor = items.size() - 1;
|
||||||
else
|
else
|
||||||
cursor--;
|
cursor--;
|
||||||
} while (items.at(cursor).isHeader);
|
} while (items.at(cursor).isHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavDown()
|
void InkHUD::MenuApplet::onNavDown()
|
||||||
{
|
{
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
if (!freeTextMode) {
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
if (!cursorShown) {
|
if (!cursorShown) {
|
||||||
cursorShown = true;
|
cursorShown = true;
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
cursor = (cursor + 1) % items.size();
|
cursor = (cursor + 1) % items.size();
|
||||||
} while (items.at(cursor).isHeader);
|
} while (items.at(cursor).isHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavLeft()
|
void InkHUD::MenuApplet::onNavLeft()
|
||||||
{
|
{
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
if (!freeTextMode) {
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
// Go to the previous menu page
|
// Go to the previous menu page
|
||||||
showPage(previousPage);
|
showPage(previousPage);
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavRight()
|
void InkHUD::MenuApplet::onNavRight()
|
||||||
{
|
{
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
if (!freeTextMode) {
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
if (cursorShown)
|
||||||
|
execute(items.at(cursor));
|
||||||
|
if (!wantsToRender())
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cursorShown)
|
void InkHUD::MenuApplet::onFreeText(char c)
|
||||||
execute(items.at(cursor));
|
{
|
||||||
if (!wantsToRender())
|
if (cm.freeTextItem.rawText.length() >= menuTextLimit && c != '\b')
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
return;
|
||||||
|
if (c == '\b') {
|
||||||
|
if (!cm.freeTextItem.rawText.empty())
|
||||||
|
cm.freeTextItem.rawText.pop_back();
|
||||||
|
} else {
|
||||||
|
cm.freeTextItem.rawText += c;
|
||||||
|
}
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::MenuApplet::onFreeTextDone()
|
||||||
|
{
|
||||||
|
// Restart the auto-close timeout
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
OSThread::enabled = true;
|
||||||
|
|
||||||
|
handleFreeText = false;
|
||||||
|
freeTextMode = false;
|
||||||
|
|
||||||
|
if (!cm.freeTextItem.rawText.empty()) {
|
||||||
|
cm.selectedMessageItem = &cm.freeTextItem;
|
||||||
|
showPage(MenuPage::CANNEDMESSAGE_RECIPIENT);
|
||||||
|
}
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::MenuApplet::onFreeTextCancel()
|
||||||
|
{
|
||||||
|
// Restart the auto-close timeout
|
||||||
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
OSThread::enabled = true;
|
||||||
|
|
||||||
|
handleFreeText = false;
|
||||||
|
freeTextMode = false;
|
||||||
|
|
||||||
|
// Clear the free text message
|
||||||
|
cm.freeTextItem.rawText.erase();
|
||||||
|
|
||||||
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
|
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
|
||||||
@@ -1635,6 +1726,10 @@ void InkHUD::MenuApplet::populateSendPage()
|
|||||||
// Position / NodeInfo packet
|
// Position / NodeInfo packet
|
||||||
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
|
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
|
||||||
|
|
||||||
|
// If joystick is available, include the Free Text option
|
||||||
|
if (settings->joystick.enabled)
|
||||||
|
items.push_back(MenuItem("Free Text", MenuAction::FREE_TEXT, MenuPage::SEND));
|
||||||
|
|
||||||
// One menu item for each canned message
|
// One menu item for each canned message
|
||||||
uint8_t count = cm.store->size();
|
uint8_t count = cm.store->size();
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
@@ -1734,6 +1829,48 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Draw the text, input box, and cursor
|
||||||
|
// Adjusting the box for screen height
|
||||||
|
while (wrapMaxH < height - fontSmall.lineHeight()) {
|
||||||
|
wrapMaxH += fontSmall.lineHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the text is so long that it goes outside of the input box, the text is actually rendered off screen.
|
||||||
|
uint32_t textHeight = getWrappedTextHeight(0, width - 5, text);
|
||||||
|
if (!text.empty()) {
|
||||||
|
uint16_t textPadding = X(1.0) > Y(1.0) ? wrapMaxH - textHeight : wrapMaxH - textHeight + 1;
|
||||||
|
if (textHeight > wrapMaxH)
|
||||||
|
printWrapped(2, textPadding, width - 5, text);
|
||||||
|
else
|
||||||
|
printWrapped(2, top + 2, width - 5, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t textCursorX = text.empty() ? 1 : getCursorX();
|
||||||
|
uint16_t textCursorY = text.empty() ? fontSmall.lineHeight() + 2 : getCursorY() - fontSmall.lineHeight() + 3;
|
||||||
|
|
||||||
|
if (textCursorX + 1 > width - 5) {
|
||||||
|
textCursorX = getCursorX() - width + 5;
|
||||||
|
textCursorY += fontSmall.lineHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
fillRect(textCursorX + 1, textCursorY, 1, fontSmall.lineHeight(), BLACK);
|
||||||
|
|
||||||
|
// A white rectangle clears the top part of the screen for any text that's printed beyond the input box
|
||||||
|
fillRect(0, 0, X(1.0), top, WHITE);
|
||||||
|
|
||||||
|
// Draw character limit
|
||||||
|
std::string ftlen = std::to_string(text.length()) + "/" + to_string(menuTextLimit);
|
||||||
|
uint16_t textLen = getTextWidth(ftlen);
|
||||||
|
printAt(X(1.0) - textLen - 2, 0, ftlen);
|
||||||
|
|
||||||
|
// Draw the border
|
||||||
|
drawRect(0, top, width, wrapMaxH + 5, BLACK);
|
||||||
|
}
|
||||||
// Renders the panel shown at the top of the root menu.
|
// Renders the panel shown at the top of the root menu.
|
||||||
// Displays the clock, and several other pieces of instantaneous system info,
|
// Displays the clock, and several other pieces of instantaneous system info,
|
||||||
// which we'd prefer not to have displayed in a normal applet, as they update too frequently.
|
// which we'd prefer not to have displayed in a normal applet, as they update too frequently.
|
||||||
@@ -1875,4 +2012,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources()
|
|||||||
cm.messageItems.clear();
|
cm.messageItems.clear();
|
||||||
cm.recipientItems.clear();
|
cm.recipientItems.clear();
|
||||||
}
|
}
|
||||||
#endif // MESHTASTIC_INCLUDE_INKHUD
|
#endif // MESHTASTIC_INCLUDE_INKHUD
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void onNavDown() override;
|
void onNavDown() override;
|
||||||
void onNavLeft() override;
|
void onNavLeft() override;
|
||||||
void onNavRight() override;
|
void onNavRight() override;
|
||||||
void onRender() override;
|
void onFreeText(char c) override;
|
||||||
|
void onFreeTextDone() override;
|
||||||
|
void onFreeTextCancel() override;
|
||||||
|
void onRender(bool full) override;
|
||||||
|
|
||||||
void show(Tile *t); // Open the menu, onto a user tile
|
void show(Tile *t); // Open the menu, onto a user tile
|
||||||
void setStartPage(MenuPage page);
|
void setStartPage(MenuPage page);
|
||||||
@@ -51,6 +54,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
|
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
|
||||||
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
|
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,
|
||||||
|
std::string text); // Draw input field for free text
|
||||||
uint16_t getSystemInfoPanelHeight();
|
uint16_t getSystemInfoPanelHeight();
|
||||||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||||
@@ -62,8 +67,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
MenuPage previousPage = MenuPage::EXIT;
|
MenuPage previousPage = MenuPage::EXIT;
|
||||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||||
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
||||||
|
bool freeTextMode = false;
|
||||||
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
||||||
|
uint16_t menuTextLimit = 200;
|
||||||
|
|
||||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||||
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
|
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
|
||||||
@@ -104,6 +110,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
// Cleared onBackground (when MenuApplet closes)
|
// Cleared onBackground (when MenuApplet closes)
|
||||||
std::vector<MessageItem> messageItems;
|
std::vector<MessageItem> messageItems;
|
||||||
std::vector<RecipientItem> recipientItems;
|
std::vector<RecipientItem> recipientItems;
|
||||||
|
|
||||||
|
MessageItem freeTextItem;
|
||||||
} cm;
|
} cm;
|
||||||
|
|
||||||
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onRender()
|
void InkHUD::NotificationApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Clear the region beneath the tile
|
// Clear the region beneath the tile
|
||||||
// Most applets are drawing onto an empty frame buffer and don't need to do this
|
// Most applets are drawing onto an empty frame buffer and don't need to do this
|
||||||
@@ -139,54 +139,47 @@ void InkHUD::NotificationApplet::onForeground()
|
|||||||
void InkHUD::NotificationApplet::onBackground()
|
void InkHUD::NotificationApplet::onBackground()
|
||||||
{
|
{
|
||||||
handleInput = false;
|
handleInput = false;
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onButtonShortPress()
|
void InkHUD::NotificationApplet::onButtonShortPress()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onButtonLongPress()
|
void InkHUD::NotificationApplet::onButtonLongPress()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitShort()
|
void InkHUD::NotificationApplet::onExitShort()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitLong()
|
void InkHUD::NotificationApplet::onExitLong()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavUp()
|
void InkHUD::NotificationApplet::onNavUp()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavDown()
|
void InkHUD::NotificationApplet::onNavDown()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavLeft()
|
void InkHUD::NotificationApplet::onNavLeft()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavRight()
|
void InkHUD::NotificationApplet::onNavRight()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
|
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
NotificationApplet();
|
NotificationApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet()
|
|||||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::PairingApplet::onRender()
|
void InkHUD::PairingApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Header
|
// Header
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
@@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
PairingApplet();
|
PairingApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::PlaceholderApplet::onRender()
|
void InkHUD::PlaceholderApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// This placeholder applet fills its area with sparse diagonal lines
|
// This placeholder applet fills its area with sparse diagonal lines
|
||||||
hatchRegion(0, 0, width(), height(), 8, BLACK);
|
hatchRegion(0, 0, width(), height(), 8, BLACK);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
class PlaceholderApplet : public SystemApplet
|
class PlaceholderApplet : public SystemApplet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
||||||
// The window manager decides when and where it should be rendered
|
// The window manager decides when and where it should be rendered
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ InkHUD::TipsApplet::TipsApplet()
|
|||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::TipsApplet::onRender()
|
void InkHUD::TipsApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
switch (tipQueue.front()) {
|
switch (tipQueue.front()) {
|
||||||
case Tip::WELCOME:
|
case Tip::WELCOME:
|
||||||
@@ -261,7 +261,7 @@ void InkHUD::TipsApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// While our SystemApplet::handleInput flag is true
|
// While our SystemApplet::handleInput flag is true
|
||||||
@@ -292,9 +292,8 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||||||
inkhud->persistence->saveSettings();
|
inkhud->persistence->saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close applet and clean the screen
|
// Close applet
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
} else {
|
} else {
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
@@ -306,4 +305,4 @@ void InkHUD::TipsApplet::onExitShort()
|
|||||||
onButtonShortPress();
|
onButtonShortPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TipsApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
TipsApplet();
|
TipsApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AllMessageApplet::onRender()
|
void InkHUD::AllMessageApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Find newest message, regardless of whether DM or broadcast
|
// Find newest message, regardless of whether DM or broadcast
|
||||||
MessageStore::Message *message;
|
MessageStore::Message *message;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Applet;
|
|||||||
class AllMessageApplet : public Applet
|
class AllMessageApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::DMApplet::onRender()
|
void InkHUD::DMApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Abort if no text message
|
// Abort if no text message
|
||||||
if (!latestMessage->dm.sender) {
|
if (!latestMessage->dm.sender) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Applet;
|
|||||||
class DMApplet : public Applet
|
class DMApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::PositionsApplet::onRender()
|
void InkHUD::PositionsApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// Draw the usual map applet first
|
// Draw the usual map applet first
|
||||||
MapApplet::onRender();
|
MapApplet::onRender(full);
|
||||||
|
|
||||||
// Draw our latest "node of interest" as a special marker
|
// Draw our latest "node of interest" as a special marker
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
|
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex)
|
|||||||
store = new MessageStore("ch" + to_string(channelIndex));
|
store = new MessageStore("ch" + to_string(channelIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::ThreadedMessageApplet::onRender()
|
void InkHUD::ThreadedMessageApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
// =============
|
// =============
|
||||||
// Draw a header
|
// Draw a header
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
|
|||||||
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
||||||
ThreadedMessageApplet() = delete;
|
ThreadedMessageApplet() = delete;
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -238,6 +238,39 @@ void InkHUD::Events::onNavRight()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InkHUD::Events::onFreeText(char c)
|
||||||
|
{
|
||||||
|
// Trigger the first system applet that wants to handle the new character
|
||||||
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
|
if (sa->handleFreeText) {
|
||||||
|
sa->onFreeText(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::Events::onFreeTextDone()
|
||||||
|
{
|
||||||
|
// Trigger the first system applet that wants to handle it
|
||||||
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
|
if (sa->handleFreeText) {
|
||||||
|
sa->onFreeTextDone();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::Events::onFreeTextCancel()
|
||||||
|
{
|
||||||
|
// Trigger the first system applet that wants to handle it
|
||||||
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
|
if (sa->handleFreeText) {
|
||||||
|
sa->onFreeTextCancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Callback for deepSleepObserver
|
// Callback for deepSleepObserver
|
||||||
// Returns 0 to signal that we agree to sleep now
|
// Returns 0 to signal that we agree to sleep now
|
||||||
int InkHUD::Events::beforeDeepSleep(void *unused)
|
int InkHUD::Events::beforeDeepSleep(void *unused)
|
||||||
@@ -266,7 +299,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
|||||||
// then prepared a final powered-off screen for us, which shows device shortname.
|
// then prepared a final powered-off screen for us, which shows device shortname.
|
||||||
// We're updating to show that one now.
|
// We're updating to show that one now.
|
||||||
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false);
|
||||||
delay(1000); // Cooldown, before potentially yanking display power
|
delay(1000); // Cooldown, before potentially yanking display power
|
||||||
|
|
||||||
// InkHUD shutdown complete
|
// InkHUD shutdown complete
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ class Events
|
|||||||
void onNavLeft(); // Navigate left
|
void onNavLeft(); // Navigate left
|
||||||
void onNavRight(); // Navigate right
|
void onNavRight(); // Navigate right
|
||||||
|
|
||||||
|
// Free text typing events
|
||||||
|
void onFreeText(char c); // New freetext character input
|
||||||
|
void onFreeTextDone();
|
||||||
|
void onFreeTextCancel();
|
||||||
|
|
||||||
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
||||||
int beforeReboot(void *unused); // Prepare for reboot
|
int beforeReboot(void *unused); // Prepare for reboot
|
||||||
int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message
|
int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message
|
||||||
|
|||||||
@@ -175,6 +175,25 @@ void InkHUD::InkHUD::navRight()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call this for keyboard input
|
||||||
|
// The Keyboard Applet also calls this
|
||||||
|
void InkHUD::InkHUD::freeText(char c)
|
||||||
|
{
|
||||||
|
events->onFreeText(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this to complete a freetext input
|
||||||
|
void InkHUD::InkHUD::freeTextDone()
|
||||||
|
{
|
||||||
|
events->onFreeTextDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this to cancel a freetext input
|
||||||
|
void InkHUD::InkHUD::freeTextCancel()
|
||||||
|
{
|
||||||
|
events->onFreeTextCancel();
|
||||||
|
}
|
||||||
|
|
||||||
// Cycle the next user applet to the foreground
|
// Cycle the next user applet to the foreground
|
||||||
// Only activated applets are cycled
|
// Only activated applets are cycled
|
||||||
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
||||||
@@ -204,6 +223,18 @@ void InkHUD::InkHUD::openAlignStick()
|
|||||||
windowManager->openAlignStick();
|
windowManager->openAlignStick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open the on-screen keyboard
|
||||||
|
void InkHUD::InkHUD::openKeyboard()
|
||||||
|
{
|
||||||
|
windowManager->openKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the on-screen keyboard
|
||||||
|
void InkHUD::InkHUD::closeKeyboard()
|
||||||
|
{
|
||||||
|
windowManager->closeKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
// In layouts where multiple applets are shown at once, change which tile is focused
|
// In layouts where multiple applets are shown at once, change which tile is focused
|
||||||
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
|
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
|
||||||
void InkHUD::InkHUD::nextTile()
|
void InkHUD::InkHUD::nextTile()
|
||||||
@@ -252,10 +283,11 @@ void InkHUD::InkHUD::requestUpdate()
|
|||||||
// Ignores all diplomacy:
|
// Ignores all diplomacy:
|
||||||
// - the display *will* update
|
// - the display *will* update
|
||||||
// - the specified update type *will* be used
|
// - the specified update type *will* be used
|
||||||
|
// If the all parameter is true, the whole screen buffer is cleared and re-rendered
|
||||||
// If the async parameter is false, code flow is blocked while the update takes place
|
// If the async parameter is false, code flow is blocked while the update takes place
|
||||||
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
|
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async)
|
||||||
{
|
{
|
||||||
renderer->forceUpdate(type, async);
|
renderer->forceUpdate(type, all, async);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for any in-progress display update to complete before continuing
|
// Wait for any in-progress display update to complete before continuing
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ class InkHUD
|
|||||||
void navLeft();
|
void navLeft();
|
||||||
void navRight();
|
void navRight();
|
||||||
|
|
||||||
|
// Freetext handlers
|
||||||
|
void freeText(char c);
|
||||||
|
void freeTextDone();
|
||||||
|
void freeTextCancel();
|
||||||
|
|
||||||
// Trigger UI changes
|
// Trigger UI changes
|
||||||
// - called by various InkHUD components
|
// - called by various InkHUD components
|
||||||
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
|
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
|
||||||
@@ -71,6 +76,8 @@ class InkHUD
|
|||||||
void prevApplet();
|
void prevApplet();
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
void openAlignStick();
|
||||||
|
void openKeyboard();
|
||||||
|
void closeKeyboard();
|
||||||
void nextTile();
|
void nextTile();
|
||||||
void prevTile();
|
void prevTile();
|
||||||
void rotate();
|
void rotate();
|
||||||
@@ -84,7 +91,8 @@ class InkHUD
|
|||||||
// - called by various InkHUD components
|
// - called by various InkHUD components
|
||||||
|
|
||||||
void requestUpdate();
|
void requestUpdate();
|
||||||
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
|
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false,
|
||||||
|
bool async = true);
|
||||||
void awaitUpdate();
|
void awaitUpdate();
|
||||||
|
|
||||||
// (Re)configuring WindowManager
|
// (Re)configuring WindowManager
|
||||||
|
|||||||
@@ -56,15 +56,16 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul
|
|||||||
|
|
||||||
void InkHUD::Renderer::begin()
|
void InkHUD::Renderer::begin()
|
||||||
{
|
{
|
||||||
forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a flag, which will be picked up by runOnce, ASAP.
|
// Set a flag, which will be picked up by runOnce, ASAP.
|
||||||
// Quite likely, multiple applets will all want to respond to one event (Observable, etc)
|
// Quite likely, multiple applets will all want to respond to one event (Observable, etc)
|
||||||
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
|
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
|
||||||
void InkHUD::Renderer::requestUpdate()
|
void InkHUD::Renderer::requestUpdate(bool all)
|
||||||
{
|
{
|
||||||
requested = true;
|
requested = true;
|
||||||
|
renderAll |= all;
|
||||||
|
|
||||||
// We will run the thread as soon as we loop(),
|
// We will run the thread as soon as we loop(),
|
||||||
// after all Applets have had a chance to observe whatever event set this off
|
// after all Applets have had a chance to observe whatever event set this off
|
||||||
@@ -79,10 +80,11 @@ void InkHUD::Renderer::requestUpdate()
|
|||||||
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
|
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
|
||||||
// Display health, for example.
|
// Display health, for example.
|
||||||
// In these situations, we use forceUpdate
|
// In these situations, we use forceUpdate
|
||||||
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async)
|
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async)
|
||||||
{
|
{
|
||||||
requested = true;
|
requested = true;
|
||||||
forced = true;
|
forced = true;
|
||||||
|
renderAll |= all;
|
||||||
displayHealth.forceUpdateType(type);
|
displayHealth.forceUpdateType(type);
|
||||||
|
|
||||||
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update
|
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update
|
||||||
@@ -219,7 +221,8 @@ void InkHUD::Renderer::render(bool async)
|
|||||||
Drivers::EInk::UpdateTypes updateType = decideUpdateType();
|
Drivers::EInk::UpdateTypes updateType = decideUpdateType();
|
||||||
|
|
||||||
// Render the new image
|
// Render the new image
|
||||||
clearBuffer();
|
if (renderAll)
|
||||||
|
clearBuffer();
|
||||||
renderUserApplets();
|
renderUserApplets();
|
||||||
renderPlaceholders();
|
renderPlaceholders();
|
||||||
renderSystemApplets();
|
renderSystemApplets();
|
||||||
@@ -247,6 +250,7 @@ void InkHUD::Renderer::render(bool async)
|
|||||||
// Tidy up, ready for a new request
|
// Tidy up, ready for a new request
|
||||||
requested = false;
|
requested = false;
|
||||||
forced = false;
|
forced = false;
|
||||||
|
renderAll = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually fill the image buffer with WHITE
|
// Manually fill the image buffer with WHITE
|
||||||
@@ -259,6 +263,76 @@ void InkHUD::Renderer::clearBuffer()
|
|||||||
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
|
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually clear the pixels below a tile
|
||||||
|
void InkHUD::Renderer::clearTile(Tile *t)
|
||||||
|
{
|
||||||
|
// Rotate the tile dimensions
|
||||||
|
int16_t left = 0;
|
||||||
|
int16_t top = 0;
|
||||||
|
uint16_t width = 0;
|
||||||
|
uint16_t height = 0;
|
||||||
|
switch (settings->rotation) {
|
||||||
|
case 0:
|
||||||
|
left = t->getLeft();
|
||||||
|
top = t->getTop();
|
||||||
|
width = t->getWidth();
|
||||||
|
height = t->getHeight();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
left = driver->width - (t->getTop() + t->getHeight());
|
||||||
|
top = t->getLeft();
|
||||||
|
width = t->getHeight();
|
||||||
|
height = t->getWidth();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
left = driver->width - (t->getLeft() + t->getWidth());
|
||||||
|
top = driver->height - (t->getTop() + t->getHeight());
|
||||||
|
width = t->getWidth();
|
||||||
|
height = t->getHeight();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
left = t->getTop();
|
||||||
|
top = driver->height - (t->getLeft() + 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 + width < 0 || top + height < 0)
|
||||||
|
return; // the box is completely off the screen
|
||||||
|
uint16_t xEnd = left + width;
|
||||||
|
uint16_t yEnd = top + height;
|
||||||
|
if (xEnd > driver->width)
|
||||||
|
xEnd = driver->width;
|
||||||
|
if (yEnd > driver->height)
|
||||||
|
yEnd = driver->height;
|
||||||
|
|
||||||
|
// Clear the pixels
|
||||||
|
if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear
|
||||||
|
memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth);
|
||||||
|
} else {
|
||||||
|
const uint16_t byteStart = (xStart / 8) + 1;
|
||||||
|
const uint16_t byteEnd = xEnd / 8;
|
||||||
|
const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8));
|
||||||
|
const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF;
|
||||||
|
for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) {
|
||||||
|
// Set the leading byte
|
||||||
|
imageBuffer[i + byteStart - 1] |= leadingByte;
|
||||||
|
|
||||||
|
// Set the continuous bytes
|
||||||
|
if (byteStart < byteEnd)
|
||||||
|
memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart);
|
||||||
|
|
||||||
|
// Set the trailing byte
|
||||||
|
if (byteEnd != imageBufferWidth)
|
||||||
|
imageBuffer[i + byteEnd] |= trailingByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InkHUD::Renderer::checkLocks()
|
void InkHUD::Renderer::checkLocks()
|
||||||
{
|
{
|
||||||
lockRendering = nullptr;
|
lockRendering = nullptr;
|
||||||
@@ -323,12 +397,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType()
|
|||||||
if (!forced) {
|
if (!forced) {
|
||||||
// User applets
|
// User applets
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
for (Applet *ua : inkhud->userApplets) {
|
||||||
if (ua && ua->isForeground())
|
if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll))
|
||||||
displayHealth.requestUpdateType(ua->wantsUpdateType());
|
displayHealth.requestUpdateType(ua->wantsUpdateType());
|
||||||
}
|
}
|
||||||
// System Applets
|
// System Applets
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
if (sa && sa->isForeground())
|
if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll))
|
||||||
displayHealth.requestUpdateType(sa->wantsUpdateType());
|
displayHealth.requestUpdateType(sa->wantsUpdateType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,9 +420,16 @@ void InkHUD::Renderer::renderUserApplets()
|
|||||||
|
|
||||||
// Render any user applets which are currently visible
|
// Render any user applets which are currently visible
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
for (Applet *ua : inkhud->userApplets) {
|
||||||
if (ua && ua->isActive() && ua->isForeground()) {
|
if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) {
|
||||||
|
|
||||||
|
// Clear the tile unless the applet wants to draw over its previous render
|
||||||
|
// or everything is getting re-rendered anyways
|
||||||
|
if (ua->wantsFullRender() && !renderAll)
|
||||||
|
clearTile(ua->getTile());
|
||||||
|
|
||||||
uint32_t start = millis();
|
uint32_t start = millis();
|
||||||
ua->render(); // Draw!
|
bool full = ua->wantsFullRender() || renderAll;
|
||||||
|
ua->render(full); // Draw!
|
||||||
uint32_t stop = millis();
|
uint32_t stop = millis();
|
||||||
LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
|
LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
|
||||||
}
|
}
|
||||||
@@ -370,6 +451,9 @@ void InkHUD::Renderer::renderSystemApplets()
|
|||||||
if (!sa->isForeground())
|
if (!sa->isForeground())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll)
|
||||||
|
continue;
|
||||||
|
|
||||||
// Skip if locked by another applet
|
// Skip if locked by another applet
|
||||||
if (lockRendering && lockRendering != sa)
|
if (lockRendering && lockRendering != sa)
|
||||||
continue;
|
continue;
|
||||||
@@ -381,8 +465,14 @@ void InkHUD::Renderer::renderSystemApplets()
|
|||||||
|
|
||||||
assert(sa->getTile());
|
assert(sa->getTile());
|
||||||
|
|
||||||
|
// Clear the tile unless the applet wants to draw over its previous render
|
||||||
|
// or everything is getting re-rendered anyways
|
||||||
|
if (sa->wantsFullRender() && !renderAll)
|
||||||
|
clearTile(sa->getTile());
|
||||||
|
|
||||||
// uint32_t start = millis();
|
// uint32_t start = millis();
|
||||||
sa->render(); // Draw!
|
bool full = sa->wantsFullRender() || renderAll;
|
||||||
|
sa->render(full); // Draw!
|
||||||
// uint32_t stop = millis();
|
// uint32_t stop = millis();
|
||||||
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
|
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
|
||||||
}
|
}
|
||||||
@@ -409,7 +499,10 @@ void InkHUD::Renderer::renderPlaceholders()
|
|||||||
// uint32_t start = millis();
|
// uint32_t start = millis();
|
||||||
for (Tile *t : emptyTiles) {
|
for (Tile *t : emptyTiles) {
|
||||||
t->assignApplet(placeholder);
|
t->assignApplet(placeholder);
|
||||||
placeholder->render();
|
// Clear the tile unless everything is getting re-rendered
|
||||||
|
if (!renderAll)
|
||||||
|
clearTile(t);
|
||||||
|
placeholder->render(true); // full render
|
||||||
t->assignApplet(nullptr);
|
t->assignApplet(nullptr);
|
||||||
}
|
}
|
||||||
// uint32_t stop = millis();
|
// uint32_t stop = millis();
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread
|
|||||||
|
|
||||||
// Call these to make the image change
|
// Call these to make the image change
|
||||||
|
|
||||||
void requestUpdate(); // Update display, if a foreground applet has info it wants to show
|
void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show
|
||||||
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED,
|
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false,
|
||||||
bool async = true); // Update display, regardless of whether any applets requested this
|
bool async = true); // Update display, regardless of whether any applets requested this
|
||||||
|
|
||||||
// Wait for an update to complete
|
// Wait for an update to complete
|
||||||
@@ -65,6 +65,7 @@ class Renderer : protected concurrency::OSThread
|
|||||||
// Steps of the rendering process
|
// Steps of the rendering process
|
||||||
|
|
||||||
void clearBuffer();
|
void clearBuffer();
|
||||||
|
void clearTile(Tile *t);
|
||||||
void checkLocks();
|
void checkLocks();
|
||||||
bool shouldUpdate();
|
bool shouldUpdate();
|
||||||
Drivers::EInk::UpdateTypes decideUpdateType();
|
Drivers::EInk::UpdateTypes decideUpdateType();
|
||||||
@@ -85,6 +86,7 @@ class Renderer : protected concurrency::OSThread
|
|||||||
|
|
||||||
bool requested = false;
|
bool requested = false;
|
||||||
bool forced = false;
|
bool forced = false;
|
||||||
|
bool renderAll = false;
|
||||||
|
|
||||||
// For convenience
|
// For convenience
|
||||||
InkHUD *inkhud = nullptr;
|
InkHUD *inkhud = nullptr;
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ class SystemApplet : public Applet
|
|||||||
public:
|
public:
|
||||||
// System applets have the right to:
|
// System applets have the right to:
|
||||||
|
|
||||||
bool handleInput = false; // - respond to input from the user button
|
bool handleInput = false; // - respond to input from the user button
|
||||||
bool lockRendering = false; // - prevent other applets from being rendered during an update
|
bool handleFreeText = false; // - respond to free text input
|
||||||
bool lockRequests = false; // - prevent other applets from triggering display updates
|
bool lockRendering = false; // - prevent other applets from being rendered during an update
|
||||||
|
bool lockRequests = false; // - prevent other applets from triggering display updates
|
||||||
|
bool alwaysRender = false; // - render every time the screen is updated
|
||||||
|
|
||||||
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
||||||
virtual void onApplyingChanges() {}
|
virtual void onApplyingChanges() {}
|
||||||
@@ -41,4 +43,4 @@ class SystemApplet : public Applet
|
|||||||
|
|
||||||
}; // namespace NicheGraphics::InkHUD
|
}; // namespace NicheGraphics::InkHUD
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ static int32_t runtaskHighlight()
|
|||||||
LOG_DEBUG("Dismissing Highlight");
|
LOG_DEBUG("Dismissing Highlight");
|
||||||
InkHUD::Tile::highlightShown = false;
|
InkHUD::Tile::highlightShown = false;
|
||||||
InkHUD::Tile::highlightTarget = nullptr;
|
InkHUD::Tile::highlightTarget = nullptr;
|
||||||
InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting
|
InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting
|
||||||
return taskHighlight->disable();
|
return taskHighlight->disable();
|
||||||
}
|
}
|
||||||
static void inittaskHighlight()
|
static void inittaskHighlight()
|
||||||
@@ -190,6 +190,18 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used in Renderer for clearing the tile
|
||||||
|
int16_t InkHUD::Tile::getLeft()
|
||||||
|
{
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in Renderer for clearing the tile
|
||||||
|
int16_t InkHUD::Tile::getTop()
|
||||||
|
{
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
// Called by Applet base class, when setting applet dimensions, immediately before render
|
// Called by Applet base class, when setting applet dimensions, immediately before render
|
||||||
uint16_t InkHUD::Tile::getWidth()
|
uint16_t InkHUD::Tile::getWidth()
|
||||||
{
|
{
|
||||||
@@ -220,7 +232,7 @@ void InkHUD::Tile::requestHighlight()
|
|||||||
{
|
{
|
||||||
Tile::highlightTarget = this;
|
Tile::highlightTarget = this;
|
||||||
Tile::highlightShown = false;
|
Tile::highlightShown = false;
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first
|
// Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class Tile
|
|||||||
void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout
|
void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout
|
||||||
void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually
|
void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually
|
||||||
void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet
|
void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet
|
||||||
|
int16_t getLeft();
|
||||||
|
int16_t getTop();
|
||||||
uint16_t getWidth();
|
uint16_t getWidth();
|
||||||
uint16_t getHeight();
|
uint16_t getHeight();
|
||||||
static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter
|
static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "./Applets/System/AlignStick/AlignStickApplet.h"
|
#include "./Applets/System/AlignStick/AlignStickApplet.h"
|
||||||
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
||||||
|
#include "./Applets/System/Keyboard/KeyboardApplet.h"
|
||||||
#include "./Applets/System/Logo/LogoApplet.h"
|
#include "./Applets/System/Logo/LogoApplet.h"
|
||||||
#include "./Applets/System/Menu/MenuApplet.h"
|
#include "./Applets/System/Menu/MenuApplet.h"
|
||||||
#include "./Applets/System/Notification/NotificationApplet.h"
|
#include "./Applets/System/Notification/NotificationApplet.h"
|
||||||
@@ -148,6 +149,28 @@ void InkHUD::WindowManager::openAlignStick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InkHUD::WindowManager::openKeyboard()
|
||||||
|
{
|
||||||
|
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
|
||||||
|
|
||||||
|
if (keyboard) {
|
||||||
|
keyboard->bringToForeground();
|
||||||
|
keyboardOpen = true;
|
||||||
|
changeLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InkHUD::WindowManager::closeKeyboard()
|
||||||
|
{
|
||||||
|
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
|
||||||
|
|
||||||
|
if (keyboard) {
|
||||||
|
keyboard->sendToBackground();
|
||||||
|
keyboardOpen = false;
|
||||||
|
changeLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// On the currently focussed tile: cycle to the next available user applet
|
// On the currently focussed tile: cycle to the next available user applet
|
||||||
// Applets available for this must be activated, and not already displayed on another tile
|
// Applets available for this must be activated, and not already displayed on another tile
|
||||||
void InkHUD::WindowManager::nextApplet()
|
void InkHUD::WindowManager::nextApplet()
|
||||||
@@ -272,7 +295,6 @@ void InkHUD::WindowManager::toggleBatteryIcon()
|
|||||||
batteryIcon->sendToBackground();
|
batteryIcon->sendToBackground();
|
||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
// - redraw all applets
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +333,25 @@ void InkHUD::WindowManager::changeLayout()
|
|||||||
menu->show(ft);
|
menu->show(ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize for the on-screen keyboard
|
||||||
|
if (keyboardOpen) {
|
||||||
|
// Send all user applets to the background
|
||||||
|
// User applets currently don't handle free text input
|
||||||
|
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++)
|
||||||
|
inkhud->userApplets.at(i)->sendToBackground();
|
||||||
|
// Find the first system applet that can handle freetext and resize it
|
||||||
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
|
if (sa->handleFreeText) {
|
||||||
|
const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight();
|
||||||
|
sa->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height() - keyboardHeight - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
// - redraw all applets
|
// - redraw all applets
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform necessary reconfiguration when user activates or deactivates applets at run-time
|
// Perform necessary reconfiguration when user activates or deactivates applets at run-time
|
||||||
@@ -347,7 +385,7 @@ void InkHUD::WindowManager::changeActivatedApplets()
|
|||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
// - redraw all applets
|
// - redraw all applets
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some applets may be permitted to bring themselves to foreground, to show new data
|
// Some applets may be permitted to bring themselves to foreground, to show new data
|
||||||
@@ -433,8 +471,10 @@ void InkHUD::WindowManager::createSystemApplets()
|
|||||||
addSystemApplet("Logo", new LogoApplet, new Tile);
|
addSystemApplet("Logo", new LogoApplet, new Tile);
|
||||||
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
||||||
addSystemApplet("Tips", new TipsApplet, new Tile);
|
addSystemApplet("Tips", new TipsApplet, new Tile);
|
||||||
if (settings->joystick.enabled)
|
if (settings->joystick.enabled) {
|
||||||
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
|
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
|
||||||
|
addSystemApplet("Keyboard", new KeyboardApplet, new Tile);
|
||||||
|
}
|
||||||
|
|
||||||
addSystemApplet("Menu", new MenuApplet, nullptr);
|
addSystemApplet("Menu", new MenuApplet, nullptr);
|
||||||
|
|
||||||
@@ -457,9 +497,13 @@ void InkHUD::WindowManager::placeSystemTiles()
|
|||||||
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
if (settings->joystick.enabled)
|
if (settings->joystick.enabled) {
|
||||||
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
|
const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight();
|
||||||
|
inkhud->getSystemApplet("Keyboard")
|
||||||
|
->getTile()
|
||||||
|
->setRegion(0, inkhud->height() - keyboardHeight, inkhud->width(), keyboardHeight);
|
||||||
|
}
|
||||||
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
||||||
|
|
||||||
const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2;
|
const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2;
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class WindowManager
|
|||||||
void prevTile();
|
void prevTile();
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
void openAlignStick();
|
||||||
|
void openKeyboard();
|
||||||
|
void closeKeyboard();
|
||||||
void nextApplet();
|
void nextApplet();
|
||||||
void prevApplet();
|
void prevApplet();
|
||||||
void rotate();
|
void rotate();
|
||||||
@@ -64,6 +66,7 @@ class WindowManager
|
|||||||
void findOrphanApplets(); // Find any applets left-behind when layout changes
|
void findOrphanApplets(); // Find any applets left-behind when layout changes
|
||||||
|
|
||||||
std::vector<Tile *> userTiles; // Tiles which can host user applets
|
std::vector<Tile *> userTiles; // Tiles which can host user applets
|
||||||
|
bool keyboardOpen = false;
|
||||||
|
|
||||||
// For convenience
|
// For convenience
|
||||||
InkHUD *inkhud = nullptr;
|
InkHUD *inkhud = nullptr;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class BasicExampleApplet : public Applet
|
|||||||
// You must have an onRender() method
|
// You must have an onRender() method
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
|
|
||||||
void onRender() override;
|
void onRender(bool full) override;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ The `onRender` method is called when the display image is redrawn. This can happ
|
|||||||
```cpp
|
```cpp
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
// Our basic example doesn't do anything useful. It just passively prints some text.
|
// Our basic example doesn't do anything useful. It just passively prints some text.
|
||||||
void InkHUD::BasicExampleApplet::onRender()
|
void InkHUD::BasicExampleApplet::onRender(bool full)
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Hello, world!");
|
printAt(0, 0, "Hello, world!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
SerialKeyboard *globalSerialKeyboard = nullptr;
|
SerialKeyboard *globalSerialKeyboard = nullptr;
|
||||||
|
|
||||||
#ifdef INPUTBROKER_SERIAL_TYPE
|
#ifdef INPUTBROKER_SERIAL_TYPE
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
|
|
||||||
|
|
||||||
#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter
|
#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter
|
||||||
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
|
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
|
||||||
|
|||||||
16
src/main.cpp
16
src/main.cpp
@@ -354,9 +354,9 @@ void setup()
|
|||||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
digitalWrite(LED_POWER, LED_STATE_ON);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USER_LED
|
#ifdef LED_NOTIFICATION
|
||||||
pinMode(USER_LED, OUTPUT);
|
pinMode(LED_NOTIFICATION, OUTPUT);
|
||||||
digitalWrite(USER_LED, HIGH ^ LED_STATE_ON);
|
digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WIFI_LED
|
#ifdef WIFI_LED
|
||||||
@@ -366,11 +366,7 @@ void setup()
|
|||||||
|
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
pinMode(BLE_LED, OUTPUT);
|
pinMode(BLE_LED, OUTPUT);
|
||||||
#ifdef BLE_LED_INVERTED
|
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||||
digitalWrite(BLE_LED, HIGH);
|
|
||||||
#else
|
|
||||||
digitalWrite(BLE_LED, LOW);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
concurrency::hasBeenSetup = true;
|
concurrency::hasBeenSetup = true;
|
||||||
@@ -493,7 +489,9 @@ void setup()
|
|||||||
// The ThinkNodes have their own blink logic
|
// The ThinkNodes have their own blink logic
|
||||||
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
|
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
|
||||||
#else
|
#else
|
||||||
|
|
||||||
ledPeriodic = new Periodic("Blink", ledBlinker);
|
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fsInit();
|
fsInit();
|
||||||
@@ -834,7 +832,7 @@ void setup()
|
|||||||
SPI.begin();
|
SPI.begin();
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
// ESP32
|
// ESP32
|
||||||
#if defined(HW_SPI1_DEVICE)
|
#if defined(HW_SPI1_DEVICE)
|
||||||
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
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);
|
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
|||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
err = lora.setCodingRate(cr);
|
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
|
||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,18 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
|||||||
tosend->hop_start -= (tosend->hop_limit - 2);
|
tosend->hop_start -= (tosend->hop_limit - 2);
|
||||||
tosend->hop_limit = 2;
|
tosend->hop_limit = 2;
|
||||||
}
|
}
|
||||||
|
#elif ARCH_PORTDUINO
|
||||||
|
if (tosend->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||||
|
portduino_config.nohop_ports.size()) {
|
||||||
|
for (const auto &port : portduino_config.nohop_ports) {
|
||||||
|
if (port == tosend->decoded.portnum) {
|
||||||
|
LOG_DEBUG("0-hopping portnum %u", tosend->decoded.portnum);
|
||||||
|
tosend->hop_start -= tosend->hop_limit;
|
||||||
|
tosend->hop_limit = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
|
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
|
||||||
|
|||||||
@@ -824,16 +824,10 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.output_ms = 500;
|
moduleConfig.external_notification.output_ms = 500;
|
||||||
moduleConfig.external_notification.nag_timeout = 2;
|
moduleConfig.external_notification.nag_timeout = 2;
|
||||||
#endif
|
#endif
|
||||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
|
#if defined(LED_NOTIFICATION)
|
||||||
defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6)
|
|
||||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
|
||||||
moduleConfig.external_notification.enabled = true;
|
moduleConfig.external_notification.enabled = true;
|
||||||
moduleConfig.external_notification.output = PIN_LED2;
|
moduleConfig.external_notification.output = LED_NOTIFICATION;
|
||||||
#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
moduleConfig.external_notification.active = LED_STATE_ON;
|
||||||
moduleConfig.external_notification.active = false;
|
|
||||||
#else
|
|
||||||
moduleConfig.external_notification.active = true;
|
|
||||||
#endif
|
|
||||||
moduleConfig.external_notification.alert_message = true;
|
moduleConfig.external_notification.alert_message = true;
|
||||||
moduleConfig.external_notification.output_ms = 1000;
|
moduleConfig.external_notification.output_ms = 1000;
|
||||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||||
@@ -857,15 +851,6 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.output_ms = 100;
|
moduleConfig.external_notification.output_ms = 100;
|
||||||
moduleConfig.external_notification.active = true;
|
moduleConfig.external_notification.active = true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ELECROW_ThinkNode_M1
|
|
||||||
// Default to Elecrow USER_LED (blue)
|
|
||||||
moduleConfig.external_notification.enabled = true;
|
|
||||||
moduleConfig.external_notification.output = USER_LED;
|
|
||||||
moduleConfig.external_notification.active = true;
|
|
||||||
moduleConfig.external_notification.alert_message = true;
|
|
||||||
moduleConfig.external_notification.output_ms = 1000;
|
|
||||||
moduleConfig.external_notification.nag_timeout = 60;
|
|
||||||
#endif
|
|
||||||
#ifdef T_LORA_PAGER
|
#ifdef T_LORA_PAGER
|
||||||
moduleConfig.canned_message.updown1_enabled = true;
|
moduleConfig.canned_message.updown1_enabled = true;
|
||||||
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
||||||
@@ -1419,6 +1404,15 @@ void NodeDB::loadFromDisk()
|
|||||||
if (portduino_config.has_configDisplayMode) {
|
if (portduino_config.has_configDisplayMode) {
|
||||||
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.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 = true;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1559,6 +1553,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
|||||||
moduleConfig.has_ambient_lighting = true;
|
moduleConfig.has_ambient_lighting = true;
|
||||||
moduleConfig.has_audio = true;
|
moduleConfig.has_audio = true;
|
||||||
moduleConfig.has_paxcounter = true;
|
moduleConfig.has_paxcounter = true;
|
||||||
|
moduleConfig.has_statusmessage = true;
|
||||||
|
|
||||||
success &=
|
success &=
|
||||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#include "platform/portduino/USBHal.h"
|
#include "platform/portduino/USBHal.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_STM32WL>
|
#ifdef ARCH_STM32WL
|
||||||
#include "STM32WLE5JCInterface.h"
|
#include "STM32WLE5JCInterface.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -648,9 +648,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
|
|||||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||||
std::string out =
|
std::string out = DEBUG_PORT.mt_sprintf(
|
||||||
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
|
||||||
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
|
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
auto &s = p->decoded;
|
auto &s = p->decoded;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
|
#include "modules/Native/StoreForwardPlusPlus.h"
|
||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
||||||
@@ -365,6 +366,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
|||||||
abortSendAndNak(encodeResult, p);
|
abortSendAndNak(encodeResult, p);
|
||||||
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
||||||
}
|
}
|
||||||
|
#if ARCH_PORTDUINO
|
||||||
|
if (p_decoded->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP &&
|
||||||
|
(p->from == 0 || p->from == nodeDB->getNodeNum()) && storeForwardPlusPlusModule && portduino_config.sfpp_enabled) {
|
||||||
|
storeForwardPlusPlusModule->handleEncrypted(p_decoded, p);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||||
// Only publish to MQTT if we're the original transmitter of the packet
|
// Only publish to MQTT if we're the original transmitter of the packet
|
||||||
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
|
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
|
||||||
@@ -620,15 +627,19 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
|||||||
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
|
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
|
||||||
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
|
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
|
||||||
// Check for valid keys and single node destination
|
// Check for valid keys and single node destination
|
||||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
config.security.private_key.size == 32 && !isBroadcast(p->to) &&
|
||||||
// Check for a known public key for the destination
|
|
||||||
(node->user.public_key.size == 32) &&
|
|
||||||
// Some portnums either make no sense to send with PKC
|
// Some portnums either make no sense to send with PKC
|
||||||
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
||||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
|
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
|
||||||
LOG_DEBUG("Use PKI!");
|
LOG_DEBUG("Use PKI!");
|
||||||
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||||
return meshtastic_Routing_Error_TOO_LARGE;
|
return meshtastic_Routing_Error_TOO_LARGE;
|
||||||
|
// Check for a known public key for the destination
|
||||||
|
if (node == nullptr || node->user.public_key.size != 32) {
|
||||||
|
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
|
||||||
|
p->decoded.portnum);
|
||||||
|
return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY;
|
||||||
|
}
|
||||||
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
||||||
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
|
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
|
||||||
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
||||||
@@ -741,6 +752,22 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
cancelSending(p->from, p->id);
|
cancelSending(p->from, p->id);
|
||||||
skipHandle = true;
|
skipHandle = true;
|
||||||
}
|
}
|
||||||
|
#if ARCH_PORTDUINO
|
||||||
|
if (portduino_config.whitelist_enabled) {
|
||||||
|
bool allowed = false;
|
||||||
|
for (const auto &port : portduino_config.whitelist_ports) {
|
||||||
|
if (port == p->decoded.portnum) {
|
||||||
|
allowed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allowed) {
|
||||||
|
LOG_DEBUG("Dropping packet not on Portduino Whitelist");
|
||||||
|
cancelSending(p->from, p->id);
|
||||||
|
skipHandle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
|
|||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
err = lora.setCodingRate(cr);
|
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
|
||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
|
|||||||
@@ -905,10 +905,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
|||||||
|
|
||||||
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||||
{
|
{
|
||||||
|
bool shouldReboot = true;
|
||||||
// If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth
|
// If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth
|
||||||
// Otherwise, disable Bluetooth to prevent the phone from interfering with the config
|
// Otherwise, disable Bluetooth to prevent the phone from interfering with the config
|
||||||
if (!hasOpenEditTransaction &&
|
if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag,
|
||||||
!IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) {
|
meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) {
|
||||||
disableBluetooth();
|
disableBluetooth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,8 +1001,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
|||||||
moduleConfig.has_paxcounter = true;
|
moduleConfig.has_paxcounter = true;
|
||||||
moduleConfig.paxcounter = c.payload_variant.paxcounter;
|
moduleConfig.paxcounter = c.payload_variant.paxcounter;
|
||||||
break;
|
break;
|
||||||
|
case meshtastic_ModuleConfig_statusmessage_tag:
|
||||||
|
LOG_INFO("Set module config: StatusMessage");
|
||||||
|
moduleConfig.has_statusmessage = true;
|
||||||
|
moduleConfig.statusmessage = c.payload_variant.statusmessage;
|
||||||
|
shouldReboot = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
saveChanges(SEGMENT_MODULECONFIG);
|
saveChanges(SEGMENT_MODULECONFIG, shouldReboot);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1180,6 +1187,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
|
|||||||
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
|
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
|
||||||
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
|
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
|
||||||
break;
|
break;
|
||||||
|
case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG:
|
||||||
|
LOG_INFO("Get module config: StatusMessage");
|
||||||
|
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag;
|
||||||
|
res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.
|
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ CannedMessageModule::CannedMessageModule()
|
|||||||
: SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage")
|
: SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage")
|
||||||
{
|
{
|
||||||
this->loadProtoForModule();
|
this->loadProtoForModule();
|
||||||
if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE &&
|
if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) {
|
||||||
!CANNED_MESSAGE_MODULE_ENABLE) {
|
|
||||||
LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled");
|
LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled");
|
||||||
this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED;
|
this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED;
|
||||||
disable();
|
disable();
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ enum CannedMessageModuleIconType { shift, backspace, space, enter };
|
|||||||
#define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50
|
#define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50
|
||||||
#define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800
|
#define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800
|
||||||
|
|
||||||
#ifndef CANNED_MESSAGE_MODULE_ENABLE
|
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Data Structures
|
// Data Structures
|
||||||
// ============================
|
// ============================
|
||||||
|
|||||||
@@ -43,6 +43,9 @@
|
|||||||
#include "modules/WaypointModule.h"
|
#include "modules/WaypointModule.h"
|
||||||
#endif
|
#endif
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
|
#include "input/LinuxInputImpl.h"
|
||||||
|
#include "input/SeesawRotary.h"
|
||||||
|
#include "modules/Native/StoreForwardPlusPlus.h"
|
||||||
#include "modules/Telemetry/HostMetrics.h"
|
#include "modules/Telemetry/HostMetrics.h"
|
||||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||||
#include "modules/StoreForwardModule.h"
|
#include "modules/StoreForwardModule.h"
|
||||||
@@ -90,6 +93,9 @@
|
|||||||
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
||||||
#include "modules/DropzoneModule.h"
|
#include "modules/DropzoneModule.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
#include "modules/StatusMessageModule.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(HAS_HARDWARE_WATCHDOG)
|
#if defined(HAS_HARDWARE_WATCHDOG)
|
||||||
#include "watchdog/watchdogThread.h"
|
#include "watchdog/watchdogThread.h"
|
||||||
@@ -150,6 +156,9 @@ void setupModules()
|
|||||||
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
||||||
dropzoneModule = new DropzoneModule();
|
dropzoneModule = new DropzoneModule();
|
||||||
#endif
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
statusMessageModule = new StatusMessageModule();
|
||||||
|
#endif
|
||||||
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
|
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
|
||||||
new GenericThreadModule();
|
new GenericThreadModule();
|
||||||
#endif
|
#endif
|
||||||
@@ -171,6 +180,11 @@ void setupModules()
|
|||||||
#endif
|
#endif
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
new HostMetricsModule();
|
new HostMetricsModule();
|
||||||
|
#if SFPP_ENABLED
|
||||||
|
if (portduino_config.sfpp_enabled) {
|
||||||
|
storeForwardPlusPlusModule = new StoreForwardPlusPlusModule();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#if HAS_TELEMETRY
|
#if HAS_TELEMETRY
|
||||||
new DeviceTelemetryModule();
|
new DeviceTelemetryModule();
|
||||||
|
|||||||
2113
src/modules/Native/StoreForwardPlusPlus.cpp
Normal file
2113
src/modules/Native/StoreForwardPlusPlus.cpp
Normal file
File diff suppressed because it is too large
Load Diff
310
src/modules/Native/StoreForwardPlusPlus.h
Normal file
310
src/modules/Native/StoreForwardPlusPlus.h
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
#pragma once
|
||||||
|
#if __has_include("sqlite3.h")
|
||||||
|
#define SFPP_ENABLED 1
|
||||||
|
#include "Channels.h"
|
||||||
|
#include "ProtobufModule.h"
|
||||||
|
#include "Router.h"
|
||||||
|
#include "SinglePortModule.h"
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
|
#define SFPP_HASH_SIZE 16
|
||||||
|
#define SFPP_SHORT_HASH_SIZE 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store and forward ++ module
|
||||||
|
* There's an obvious need for a store-and-forward mechanism in Meshtastic.
|
||||||
|
* This module takes heavy inspiration from Git, building a chain of messages that can be synced between nodes.
|
||||||
|
* Each message is hashed, and the chain is built by hashing the previous commit hash and the current message hash.
|
||||||
|
* Nodes can request missing messages by requesting the next message after a given commit hash.
|
||||||
|
*
|
||||||
|
* The current focus is text messages, limited to the primary channel.
|
||||||
|
*
|
||||||
|
* Each chain is identified by a root hash, which is derived from the channelHash, the local nodenum, and the timestamp when
|
||||||
|
* created.
|
||||||
|
*
|
||||||
|
* Each message is also given a message hash, derived from the encrypted payload, the to, from, id.
|
||||||
|
* Notably not the timestamp, as we want these to match across nodes, even if the timestamps differ.
|
||||||
|
*
|
||||||
|
* The authoritative node for the chain will generate a commit hash for each message when adding it to the chain.
|
||||||
|
* The first message's commit hash is derived from the root hash and the message hash.
|
||||||
|
* Subsequent messages' commit hashes are derived from the previous commit hash and the current message hash.
|
||||||
|
* This allows a node to see only the last commit hash, and confirm it hasn't missed any messages.
|
||||||
|
*
|
||||||
|
* Nodes can request the next message in the chain by sending a LINK_REQUEST message with the root hash and the last known commit
|
||||||
|
* hash. Any node that has the next message can respond with a LINK_PROVIDE message containing the next message.
|
||||||
|
*
|
||||||
|
* When a satellite node sees a new text message, it stores it in a scratch database.
|
||||||
|
* These messages are periodically offered to the authoritative node for inclusion in the chain.
|
||||||
|
*
|
||||||
|
* The LINK_PROVIDE message does double-duty, sending both on-chain and off-chain messages.
|
||||||
|
* The differentiator is whether the commit hash is set or left empty.
|
||||||
|
*
|
||||||
|
* When a satellite node receives a canonical link message, it checks if it has the message in scratch.
|
||||||
|
* And evicts it when adding it to the canonical chain.
|
||||||
|
*
|
||||||
|
* This approach allows a node to know whether it has seen a given message before, or if it is new coming via SFPP.
|
||||||
|
* If new, and the timestamp is within the rebroadcast timeout, it will process that message as if it were just received from the
|
||||||
|
* mesh, allowing it to be decrypted, shown to the user, and rebroadcast.
|
||||||
|
*/
|
||||||
|
class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForwardPlusPlus>, private concurrency::OSThread
|
||||||
|
{
|
||||||
|
struct link_object {
|
||||||
|
uint32_t to;
|
||||||
|
uint32_t from;
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t rx_time = 0;
|
||||||
|
ChannelHash channel_hash;
|
||||||
|
uint8_t encrypted_bytes[256] = {0};
|
||||||
|
size_t encrypted_len;
|
||||||
|
uint8_t message_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t message_hash_len = 0;
|
||||||
|
uint8_t root_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t root_hash_len = 0;
|
||||||
|
uint8_t commit_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t commit_hash_len = 0;
|
||||||
|
uint32_t counter = 0;
|
||||||
|
std::string payload;
|
||||||
|
bool validObject = true; // set this false when a chain calulation fails, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructor
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
StoreForwardPlusPlusModule();
|
||||||
|
|
||||||
|
/*
|
||||||
|
-Override the wantPacket method.
|
||||||
|
*/
|
||||||
|
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
|
||||||
|
{
|
||||||
|
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||||
|
p->decoded.portnum == (portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
|
||||||
|
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEncrypted(const meshtastic_MeshPacket *, const meshtastic_MeshPacket *);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Called to handle a particular incoming message
|
||||||
|
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
|
||||||
|
it
|
||||||
|
*/
|
||||||
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override;
|
||||||
|
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3 *ppDb;
|
||||||
|
sqlite3_stmt *chain_insert_stmt;
|
||||||
|
sqlite3_stmt *scratch_insert_stmt;
|
||||||
|
sqlite3_stmt *checkDupMessageHash;
|
||||||
|
sqlite3_stmt *checkDupCommitHash;
|
||||||
|
sqlite3_stmt *checkScratch;
|
||||||
|
sqlite3_stmt *removeScratch;
|
||||||
|
sqlite3_stmt *updatePayloadStmt;
|
||||||
|
sqlite3_stmt *getPayloadFromScratchStmt;
|
||||||
|
sqlite3_stmt *fromScratchStmt;
|
||||||
|
sqlite3_stmt *fromScratchByHashStmt;
|
||||||
|
sqlite3_stmt *getNextHashStmt;
|
||||||
|
sqlite3_stmt *getChainEndStmt;
|
||||||
|
sqlite3_stmt *getLinkStmt;
|
||||||
|
sqlite3_stmt *getLinkFromMessageHashStmt;
|
||||||
|
sqlite3_stmt *getHashFromRootStmt;
|
||||||
|
sqlite3_stmt *addRootToMappingsStmt;
|
||||||
|
sqlite3_stmt *getRootFromChannelHashStmt;
|
||||||
|
sqlite3_stmt *getFullRootHashStmt;
|
||||||
|
sqlite3_stmt *setChainCountStmt;
|
||||||
|
sqlite3_stmt *getChainCountStmt;
|
||||||
|
sqlite3_stmt *getScratchCountStmt;
|
||||||
|
sqlite3_stmt *getRootCanonScratchCountStmt;
|
||||||
|
sqlite3_stmt *pruneScratchQueueStmt;
|
||||||
|
sqlite3_stmt *trimOldestLinkStmt;
|
||||||
|
sqlite3_stmt *maybeAddPeerStmt;
|
||||||
|
sqlite3_stmt *getPeerStmt;
|
||||||
|
sqlite3_stmt *updatePeerStmt;
|
||||||
|
sqlite3_stmt *clearChainStmt;
|
||||||
|
sqlite3_stmt *canon_scratch_insert_stmt;
|
||||||
|
sqlite3_stmt *getCanonScratchCountStmt;
|
||||||
|
sqlite3_stmt *getCanonScratchStmt;
|
||||||
|
sqlite3_stmt *removeCanonScratch;
|
||||||
|
sqlite3_stmt *clearCanonScratchStmt;
|
||||||
|
|
||||||
|
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
|
||||||
|
// returns true if the root hash was found
|
||||||
|
bool getRootFromChannelHash(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// For a given root hash, returns the ChannelHash
|
||||||
|
// can handle partial root hashes
|
||||||
|
ChannelHash getChannelHashFromRoot(uint8_t *_root_hash, size_t);
|
||||||
|
|
||||||
|
// given a root hash and commit hash, returns the next commit hash in the chain
|
||||||
|
// can handle partial root and commit hashes, always fills the buffer with 32 bytes
|
||||||
|
// returns true if a next hash was found
|
||||||
|
bool getNextHash(uint8_t *, size_t, uint8_t *, size_t, uint8_t *);
|
||||||
|
|
||||||
|
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
|
||||||
|
// but this function will add the root hash if it is not already present
|
||||||
|
// returns hash size or 0 if not found/added
|
||||||
|
size_t getOrAddRootFromChannelHash(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// adds the ChannelHash and root_hash to the mappings table
|
||||||
|
void addRootToMappings(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// requests the next message in the chain from the mesh network
|
||||||
|
// Sends a LINK_REQUEST message
|
||||||
|
void requestNextMessage(uint8_t *, size_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
// request the message X entries from the end.
|
||||||
|
// used to bootstrap a chain, without downloading all of the history
|
||||||
|
void requestMessageCount(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object
|
||||||
|
void broadcastLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object
|
||||||
|
void broadcastLink(link_object &, bool, bool = false);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
|
||||||
|
bool sendFromScratch(uint8_t *);
|
||||||
|
|
||||||
|
// Adds the given link object to the canonical chain database
|
||||||
|
bool addToChain(link_object &);
|
||||||
|
|
||||||
|
// Adds an incoming text message to the scratch database
|
||||||
|
bool addToScratch(link_object &);
|
||||||
|
|
||||||
|
// sends a CANON_ANNOUNCE message, specifying the given root and commit hashes
|
||||||
|
void canonAnnounce(link_object &);
|
||||||
|
|
||||||
|
// checks if the message hash is present in the canonical chain database
|
||||||
|
bool isInDB(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// checks if the commit hash is present in the canonical chain database
|
||||||
|
bool isCommitInDB(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// checks if the message hash is present in the scratch database
|
||||||
|
bool isInScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// retrieves a link object from the scratch database
|
||||||
|
link_object getFromScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// removes a link object from the scratch database
|
||||||
|
void removeFromScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// iterate through our scratch database, and see if we can speculate a chain up to the given commit hash
|
||||||
|
bool speculateScratchChain(uint8_t *, size_t, uint8_t *, uint8_t *);
|
||||||
|
|
||||||
|
// retrieves the next link object from scratch given a root hash
|
||||||
|
link_object getNextScratchObject(uint8_t *);
|
||||||
|
|
||||||
|
// fills the payload section with the decrypted data for the given message hash
|
||||||
|
// probably not needed for production, but useful for testing
|
||||||
|
void updatePayload(uint8_t *, size_t, std::string);
|
||||||
|
|
||||||
|
// Takes the decrypted MeshPacket and the encrypted packet copy, and builds a link_object
|
||||||
|
// Generates a message hash, but does not set the commit hash
|
||||||
|
link_object ingestTextPacket(const meshtastic_MeshPacket &, const meshtastic_MeshPacket *);
|
||||||
|
|
||||||
|
// ingests a LINK_PROVIDE message and builds a link_object
|
||||||
|
// confirms the root hash and commit hash
|
||||||
|
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
|
||||||
|
|
||||||
|
// retrieves a link object from the canonical chain database given a commit hash
|
||||||
|
link_object getLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// retrieves a link object from the canonical chain database given a message hash
|
||||||
|
link_object getLinkFromMessageHash(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// puts the encrypted payload back into the queue as if it were just received
|
||||||
|
void rebroadcastLinkObject(link_object &);
|
||||||
|
|
||||||
|
// Check if an incoming link object's commit hash matches the calculated commit hash
|
||||||
|
bool checkCommitHash(link_object &lo, uint8_t *commit_hash_bytes, size_t hash_len);
|
||||||
|
|
||||||
|
// given a partial root hash, looks up the full 32-byte root hash
|
||||||
|
// returns true if found
|
||||||
|
bool lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len, uint8_t *full_root_hash);
|
||||||
|
|
||||||
|
// update the mappings table to set the chain count for the given root hash
|
||||||
|
void setChainCount(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
// get the chain count for the given root hash
|
||||||
|
uint32_t getChainCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// get the scratch count for the given root hash
|
||||||
|
uint32_t getScratchCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// get the canon scratch count for the given root hash
|
||||||
|
uint32_t getCanonScratchCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
link_object getLinkFromPositionFromTip(uint32_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
void pruneScratchQueue();
|
||||||
|
|
||||||
|
void trimOldestLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void clearChain(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void recalculateMessageHash(link_object &);
|
||||||
|
|
||||||
|
// given a link object with a payload and other fields, recalculates the message hash
|
||||||
|
// returns true if a match
|
||||||
|
bool recalculateHash(link_object &, uint8_t *, size_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
void updatePeers(const meshtastic_MeshPacket &, meshtastic_StoreForwardPlusPlus_SFPP_message_type);
|
||||||
|
|
||||||
|
void maybeMoveFromCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void addToCanonScratch(link_object &);
|
||||||
|
|
||||||
|
link_object getfromCanonScratch(uint8_t *, size_t);
|
||||||
|
void removeFromCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void clearCanonScratch(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
bool isInCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void logLinkObject(link_object &);
|
||||||
|
|
||||||
|
// Track if we have a scheduled runOnce pending
|
||||||
|
// useful to not accudentally delay a scheduled runOnce
|
||||||
|
bool pendingRun = false;
|
||||||
|
|
||||||
|
// Once we have multiple chain types, we can extend this
|
||||||
|
enum chain_types {
|
||||||
|
channel_chain = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
|
||||||
|
bool doing_split_send = false;
|
||||||
|
link_object split_link_out;
|
||||||
|
|
||||||
|
bool doing_split_receive = false;
|
||||||
|
link_object split_link_in;
|
||||||
|
|
||||||
|
bool did_announce_last = false;
|
||||||
|
|
||||||
|
uint32_t texts_rebroadcast = 0;
|
||||||
|
uint32_t links_speculated = 0;
|
||||||
|
uint32_t canon_announces = 0;
|
||||||
|
uint32_t links_requested = 0;
|
||||||
|
uint32_t links_provided = 0;
|
||||||
|
uint32_t links_added = 0;
|
||||||
|
uint32_t links_from_canon_scratch = 0;
|
||||||
|
uint32_t links_from_scratch = 0;
|
||||||
|
uint32_t split_links_sent = 0;
|
||||||
|
uint32_t split_links_received = 0;
|
||||||
|
uint32_t links_pruned = 0;
|
||||||
|
uint32_t scratch_timed_out = 0;
|
||||||
|
uint32_t sent_from_scratch = 0;
|
||||||
|
uint32_t received_from_scratch = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern StoreForwardPlusPlusModule *storeForwardPlusPlusModule;
|
||||||
|
#endif
|
||||||
@@ -130,7 +130,6 @@ int32_t StatusLEDModule::runOnce()
|
|||||||
#ifdef LED_CHARGE
|
#ifdef LED_CHARGE
|
||||||
digitalWrite(LED_CHARGE, CHARGE_LED_state);
|
digitalWrite(LED_CHARGE, CHARGE_LED_state);
|
||||||
#endif
|
#endif
|
||||||
// digitalWrite(green_LED_PIN, LED_STATE_OFF);
|
|
||||||
#ifdef LED_PAIRING
|
#ifdef LED_PAIRING
|
||||||
digitalWrite(LED_PAIRING, PAIRING_LED_state);
|
digitalWrite(LED_PAIRING, PAIRING_LED_state);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
41
src/modules/StatusMessageModule.cpp
Normal file
41
src/modules/StatusMessageModule.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
|
||||||
|
#include "StatusMessageModule.h"
|
||||||
|
#include "MeshService.h"
|
||||||
|
#include "ProtobufModule.h"
|
||||||
|
|
||||||
|
StatusMessageModule *statusMessageModule;
|
||||||
|
|
||||||
|
int32_t StatusMessageModule::runOnce()
|
||||||
|
{
|
||||||
|
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
|
||||||
|
// create and send message with the status message set
|
||||||
|
meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero;
|
||||||
|
strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status));
|
||||||
|
ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination
|
||||||
|
meshtastic_MeshPacket *p = allocDataPacket();
|
||||||
|
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
|
||||||
|
meshtastic_StatusMessage_fields, &ourStatus);
|
||||||
|
p->to = NODENUM_BROADCAST;
|
||||||
|
p->decoded.want_response = false;
|
||||||
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
|
p->channel = 0;
|
||||||
|
service->sendToMesh(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1000 * 12 * 60 * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||||
|
{
|
||||||
|
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
|
meshtastic_StatusMessage incomingMessage;
|
||||||
|
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
|
||||||
|
&incomingMessage)) {
|
||||||
|
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProcessMessage::CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
35
src/modules/StatusMessageModule.h
Normal file
35
src/modules/StatusMessageModule.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
#include "SinglePortModule.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructor
|
||||||
|
* name is for debugging output
|
||||||
|
*/
|
||||||
|
StatusMessageModule()
|
||||||
|
: SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage")
|
||||||
|
{
|
||||||
|
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
|
||||||
|
this->setInterval(2 * 60 * 1000);
|
||||||
|
} else {
|
||||||
|
this->setInterval(1000 * 12 * 60 * 60);
|
||||||
|
}
|
||||||
|
// TODO: If we have a string, set the initial delay (15 minutes maybe)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Called to handle a particular incoming message
|
||||||
|
*/
|
||||||
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
extern StatusMessageModule *statusMessageModule;
|
||||||
|
#endif
|
||||||
@@ -26,7 +26,7 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
|||||||
sensor.get_sensor_version(&data);
|
sensor.get_sensor_version(&data);
|
||||||
if (data != 0) {
|
if (data != 0) {
|
||||||
LOG_INFO("Init sensor: %s", sensorName);
|
LOG_INFO("Init sensor: %s", sensorName);
|
||||||
LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName);
|
LOG_INFO("RAK12035Sensor Init Succeed \nSensor Firmware version: %i, Sensor Name: %s", data, sensorName);
|
||||||
status = true;
|
status = true;
|
||||||
sensor.sensor_sleep();
|
sensor.sensor_sleep();
|
||||||
RESTORE_3V3_POWER();
|
RESTORE_3V3_POWER();
|
||||||
@@ -49,33 +49,39 @@ void RAK12035Sensor::setup()
|
|||||||
// TODO:: Check for and run calibration check for up to 2 additional sensors if present.
|
// TODO:: Check for and run calibration check for up to 2 additional sensors if present.
|
||||||
uint16_t zero_val = 0;
|
uint16_t zero_val = 0;
|
||||||
uint16_t hundred_val = 0;
|
uint16_t hundred_val = 0;
|
||||||
uint16_t default_zero_val = 550;
|
const uint16_t default_zero_val = 510;
|
||||||
uint16_t default_hundred_val = 420;
|
const uint16_t default_hundred_val = 390;
|
||||||
|
|
||||||
sensor.sensor_on();
|
sensor.sensor_on();
|
||||||
|
sensor.begin();
|
||||||
delay(200);
|
delay(200);
|
||||||
sensor.get_dry_cal(&zero_val);
|
sensor.get_dry_cal(&zero_val);
|
||||||
|
delay(200);
|
||||||
sensor.get_wet_cal(&hundred_val);
|
sensor.get_wet_cal(&hundred_val);
|
||||||
delay(200);
|
delay(200);
|
||||||
if (zero_val == 0 || zero_val <= hundred_val) {
|
|
||||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
bool calibrationReset = false;
|
||||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
|
||||||
LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: "
|
if (zero_val == 0) {
|
||||||
"https://github.com/RAKWireless/RAK12035_SoilMoisture.");
|
LOG_INFO("Dry calibration not set, using default: %d", default_zero_val);
|
||||||
LOG_INFO("For now, setting default calibration value for Dry Calibration: %d", default_zero_val);
|
|
||||||
sensor.set_dry_cal(default_zero_val);
|
sensor.set_dry_cal(default_zero_val);
|
||||||
sensor.get_dry_cal(&zero_val);
|
delay(200);
|
||||||
LOG_INFO("Dry calibration reset complete. New value is %d", zero_val);
|
zero_val = default_zero_val;
|
||||||
|
calibrationReset = true;
|
||||||
}
|
}
|
||||||
if (hundred_val == 0 || hundred_val >= zero_val) {
|
if (hundred_val == 0 || hundred_val >= zero_val) {
|
||||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
LOG_INFO("Wet calibration not set, using default: %d", default_hundred_val);
|
||||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
|
||||||
LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: "
|
|
||||||
"https://github.com/RAKWireless/RAK12035_SoilMoisture.");
|
|
||||||
LOG_INFO("For now, setting default calibration value for Wet Calibration: %d", default_hundred_val);
|
|
||||||
sensor.set_wet_cal(default_hundred_val);
|
sensor.set_wet_cal(default_hundred_val);
|
||||||
sensor.get_wet_cal(&hundred_val);
|
delay(200);
|
||||||
LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val);
|
hundred_val = default_hundred_val;
|
||||||
|
calibrationReset = true;
|
||||||
}
|
}
|
||||||
|
if (calibrationReset) {
|
||||||
|
LOG_INFO("Default calibration values applied. Consider running the calibration sketch for better accuracy: "
|
||||||
|
"https://github.com/RAKWireless/RAK12035_SoilMoisture");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Dry calibration value: %d, Wet calibration value: %d", zero_val, hundred_val);
|
||||||
sensor.sensor_sleep();
|
sensor.sensor_sleep();
|
||||||
RESTORE_3V3_POWER();
|
RESTORE_3V3_POWER();
|
||||||
delay(200);
|
delay(200);
|
||||||
|
|||||||
@@ -757,11 +757,7 @@ void NimbleBluetooth::deinit()
|
|||||||
isDeInit = true;
|
isDeInit = true;
|
||||||
|
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
#ifdef BLE_LED_INVERTED
|
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||||
digitalWrite(BLE_LED, HIGH);
|
|
||||||
#else
|
|
||||||
digitalWrite(BLE_LED, LOW);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
#ifndef NIMBLE_TWO
|
#ifndef NIMBLE_TWO
|
||||||
NimBLEDevice::deinit();
|
NimBLEDevice::deinit();
|
||||||
|
|||||||
@@ -33,9 +33,6 @@
|
|||||||
#ifndef HAS_RADIO
|
#ifndef HAS_RADIO
|
||||||
#define HAS_RADIO 1
|
#define HAS_RADIO 1
|
||||||
#endif
|
#endif
|
||||||
#ifndef HAS_RTC
|
|
||||||
#define HAS_RTC 1
|
|
||||||
#endif
|
|
||||||
#ifndef HAS_CPU_SHUTDOWN
|
#ifndef HAS_CPU_SHUTDOWN
|
||||||
#define HAS_CPU_SHUTDOWN 1
|
#define HAS_CPU_SHUTDOWN 1
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ void portduinoSetup()
|
|||||||
|
|
||||||
if (portduino_config.force_simradio == true) {
|
if (portduino_config.force_simradio == true) {
|
||||||
portduino_config.lora_module = use_simradio;
|
portduino_config.lora_module = use_simradio;
|
||||||
|
portduino_config.sfpp_enabled = false;
|
||||||
} else if (configPath != nullptr) {
|
} else if (configPath != nullptr) {
|
||||||
if (loadConfig(configPath)) {
|
if (loadConfig(configPath)) {
|
||||||
if (!yamlOnly)
|
if (!yamlOnly)
|
||||||
@@ -872,6 +873,7 @@ bool loadConfig(const char *configPath)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (yamlConfig["Config"]) {
|
if (yamlConfig["Config"]) {
|
||||||
|
portduino_config.has_config_overrides = true;
|
||||||
if (yamlConfig["Config"]["DisplayMode"]) {
|
if (yamlConfig["Config"]["DisplayMode"]) {
|
||||||
portduino_config.has_configDisplayMode = true;
|
portduino_config.has_configDisplayMode = true;
|
||||||
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
|
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
|
||||||
@@ -884,8 +886,37 @@ bool loadConfig(const char *configPath)
|
|||||||
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
|
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (yamlConfig["Config"]["StatusMessage"]) {
|
||||||
|
portduino_config.has_statusMessage = true;
|
||||||
|
portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as<std::string>("");
|
||||||
|
}
|
||||||
|
if ((yamlConfig["Config"]["EnableUDP"]).as<bool>(false)) {
|
||||||
|
portduino_config.enable_UDP = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yamlConfig["StoreAndForward"]) {
|
||||||
|
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
|
||||||
|
portduino_config.sfpp_enabled = (yamlConfig["StoreAndForward"]["Enabled"]).as<bool>(true);
|
||||||
|
portduino_config.sfpp_db_path = (yamlConfig["StoreAndForward"]["DBPath"]).as<std::string>("/var/lib/meshtasticd/");
|
||||||
|
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
|
||||||
|
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
|
||||||
|
portduino_config.sfpp_announce_interval = (yamlConfig["StoreAndForward"]["AnnounceInterval"]).as<int>(5);
|
||||||
|
portduino_config.sfpp_max_chain = (yamlConfig["StoreAndForward"]["MaxChainLength"]).as<uint32_t>(1000);
|
||||||
|
portduino_config.sfpp_backlog_limit = (yamlConfig["StoreAndForward"]["BacklogLimit"]).as<uint32_t>(100);
|
||||||
|
portduino_config.sfpp_steal_port = (yamlConfig["StoreAndForward"]["StealPort"]).as<bool>(false);
|
||||||
|
}
|
||||||
|
if (yamlConfig["Routing"]) {
|
||||||
|
if (yamlConfig["Routing"]["WhitelistPorts"]) {
|
||||||
|
portduino_config.whitelist_ports = (yamlConfig["Routing"]["WhitelistPorts"]).as<std::vector<int>>();
|
||||||
|
if (portduino_config.whitelist_ports.size() > 0) {
|
||||||
|
portduino_config.whitelist_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yamlConfig["Routing"]["NoHopPorts"]) {
|
||||||
|
portduino_config.nohop_ports = (yamlConfig["Routing"]["NoHopPorts"]).as<std::vector<int>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (yamlConfig["General"]) {
|
if (yamlConfig["General"]) {
|
||||||
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
|
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
|
||||||
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
|
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
|
||||||
|
|||||||
@@ -177,8 +177,32 @@ extern struct portduino_config_struct {
|
|||||||
int hostMetrics_channel = 0;
|
int hostMetrics_channel = 0;
|
||||||
|
|
||||||
// config
|
// config
|
||||||
|
bool has_config_overrides = false;
|
||||||
int configDisplayMode = 0;
|
int configDisplayMode = 0;
|
||||||
bool has_configDisplayMode = false;
|
bool has_configDisplayMode = false;
|
||||||
|
std::string statusMessage = "";
|
||||||
|
bool has_statusMessage = false;
|
||||||
|
bool enable_UDP = false;
|
||||||
|
|
||||||
|
// Store and Forward++
|
||||||
|
std::string sfpp_db_path = "/var/lib/meshtasticd/";
|
||||||
|
bool sfpp_stratum0 = false;
|
||||||
|
bool sfpp_enabled = true;
|
||||||
|
bool sfpp_steal_port = false;
|
||||||
|
int sfpp_initial_sync = 10;
|
||||||
|
int sfpp_hops = 3;
|
||||||
|
int sfpp_announce_interval = 5; // minutes
|
||||||
|
uint32_t sfpp_max_chain = 1000;
|
||||||
|
uint32_t sfpp_backlog_limit = 100;
|
||||||
|
// allowed root hashes
|
||||||
|
// upstream node
|
||||||
|
// Are we allowing unknown channel hashes? Does this even make sense?
|
||||||
|
// Allow DMs
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
bool whitelist_enabled = false;
|
||||||
|
std::vector<int> whitelist_ports = {};
|
||||||
|
std::vector<int> nohop_ports = {};
|
||||||
|
|
||||||
// General
|
// General
|
||||||
std::string mac_address = "";
|
std::string mac_address = "";
|
||||||
@@ -505,26 +529,58 @@ extern struct portduino_config_struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// config
|
// config
|
||||||
if (has_configDisplayMode) {
|
if (has_config_overrides) {
|
||||||
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
|
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
|
||||||
switch (configDisplayMode) {
|
if (has_configDisplayMode) {
|
||||||
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
|
|
||||||
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
|
switch (configDisplayMode) {
|
||||||
break;
|
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
|
||||||
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
|
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
|
||||||
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
|
break;
|
||||||
break;
|
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
|
||||||
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
|
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
|
||||||
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
|
break;
|
||||||
break;
|
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
|
||||||
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
|
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
|
||||||
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
|
break;
|
||||||
break;
|
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
|
||||||
|
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_statusMessage) {
|
||||||
|
out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage;
|
||||||
|
}
|
||||||
|
if (enable_UDP) {
|
||||||
|
out << YAML::Key << "EnableUDP" << YAML::Value << true;
|
||||||
}
|
}
|
||||||
|
|
||||||
out << YAML::EndMap; // Config
|
out << YAML::EndMap; // Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreAndForward
|
||||||
|
if (sfpp_enabled) {
|
||||||
|
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "Enabled" << YAML::Value << sfpp_enabled;
|
||||||
|
out << YAML::Key << "DBPath" << YAML::Value << sfpp_db_path;
|
||||||
|
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
|
||||||
|
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
|
||||||
|
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
|
||||||
|
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
|
||||||
|
out << YAML::Key << "BacklogLimit" << YAML::Value << sfpp_backlog_limit;
|
||||||
|
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
|
||||||
|
out << YAML::Key << "StealPort" << YAML::Value << sfpp_steal_port;
|
||||||
|
out << YAML::EndMap; // StoreAndForward
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
if (whitelist_enabled || nohop_ports.size() > 0) {
|
||||||
|
out << YAML::Key << "Routing" << YAML::Value << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "WhitelistPorts" << YAML::Value << whitelist_ports;
|
||||||
|
out << YAML::Key << "NoHopPorts" << YAML::Value << nohop_ports;
|
||||||
|
out << YAML::EndMap; // Routing
|
||||||
|
}
|
||||||
|
|
||||||
// General
|
// General
|
||||||
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
|
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
|
||||||
if (config_directory != "")
|
if (config_directory != "")
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
#ifndef HAS_RADIO
|
#ifndef HAS_RADIO
|
||||||
#define HAS_RADIO 1
|
#define HAS_RADIO 1
|
||||||
#endif
|
#endif
|
||||||
#ifndef HAS_RTC
|
|
||||||
#define HAS_RTC 1
|
|
||||||
#endif
|
|
||||||
#ifndef HAS_TELEMETRY
|
#ifndef HAS_TELEMETRY
|
||||||
#define HAS_TELEMETRY 1
|
#define HAS_TELEMETRY 1
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -241,7 +241,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
|
|||||||
#ifdef PIN_POWER_EN
|
#ifdef PIN_POWER_EN
|
||||||
digitalWrite(PIN_POWER_EN, LOW);
|
digitalWrite(PIN_POWER_EN, LOW);
|
||||||
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
||||||
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef RAK_WISMESH_TAP_V2
|
#ifdef RAK_WISMESH_TAP_V2
|
||||||
|
|||||||
@@ -98,7 +98,6 @@
|
|||||||
#define KB_LOAD 21 // load values from the switch and store in shift register
|
#define KB_LOAD 21 // load values from the switch and store in shift register
|
||||||
#define KB_CLK 22 // clock pin for serial data out
|
#define KB_CLK 22 // clock pin for serial data out
|
||||||
#define KB_DATA 23 // data pin
|
#define KB_DATA 23 // data pin
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
|
|||||||
@@ -10,3 +10,6 @@ build_flags =
|
|||||||
-D EBYTE_E22
|
-D EBYTE_E22
|
||||||
-D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation
|
-D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation
|
||||||
-I variants/esp32/diy/9m2ibr_aprs_lora_tracker
|
-I variants/esp32/diy/9m2ibr_aprs_lora_tracker
|
||||||
|
build_src_filter =
|
||||||
|
${esp32_base.build_src_filter}
|
||||||
|
+<../variants/esp32/diy/9m2ibr_aprs_lora_tracker>
|
||||||
8
variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp
Normal file
8
variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "variant.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
void earlyInitVariant()
|
||||||
|
{
|
||||||
|
pinMode(USER_LED, OUTPUT);
|
||||||
|
digitalWrite(USER_LED, HIGH ^ LED_STATE_ON);
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ build_flags =
|
|||||||
-DTFT_BL=32
|
-DTFT_BL=32
|
||||||
-DSPI_FREQUENCY=40000000
|
-DSPI_FREQUENCY=40000000
|
||||||
-DSPI_READ_FREQUENCY=16000000
|
-DSPI_READ_FREQUENCY=16000000
|
||||||
-DDISABLE_ALL_LIBRARY_WARNINGS
|
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
m5stack-core
|
m5stack-core
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
// PCF8563 RTC Module
|
// PCF8563 RTC Module
|
||||||
#define PCF8563_RTC 0x51
|
#define PCF8563_RTC 0x51
|
||||||
#define HAS_RTC 1
|
|
||||||
|
|
||||||
// Wheel
|
// Wheel
|
||||||
// Down 37
|
// Down 37
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
#ifndef TOUCH_IRQ
|
#ifndef TOUCH_IRQ
|
||||||
#define TOUCH_IRQ -1
|
#define TOUCH_IRQ -1
|
||||||
#endif
|
#endif
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
#define USE_VIRTUAL_KEYBOARD 1
|
#define USE_VIRTUAL_KEYBOARD 1
|
||||||
|
|
||||||
#define ST7796_NSS 25
|
#define ST7796_NSS 25
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#define LED_PIN 13 // 13 red, 2 blue, 15 red
|
#define LED_PIN 13 // 13 red, 2 blue, 15 red
|
||||||
|
|
||||||
// #define HAS_BUTTON 0
|
|
||||||
#define BUTTON_PIN 0
|
#define BUTTON_PIN 0
|
||||||
#define BUTTON_NEED_PULLUP
|
#define BUTTON_NEED_PULLUP
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#undef GPS_TX_PIN
|
#undef GPS_TX_PIN
|
||||||
#define NO_GPS 1
|
#define NO_GPS 1
|
||||||
#define HAS_GPS 0
|
#define HAS_GPS 0
|
||||||
#define NO_SCREEN
|
|
||||||
#define HAS_SCREEN 0
|
#define HAS_SCREEN 0
|
||||||
|
|
||||||
// Default SPI1 will be mapped to the display
|
// Default SPI1 will be mapped to the display
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#define EXT_NOTIFY_OUT 22
|
#define EXT_NOTIFY_OUT 22
|
||||||
#define BUTTON_PIN 0 // 17
|
#define BUTTON_PIN 0 // 17
|
||||||
|
|
||||||
// #define LED_PIN PIN_LED
|
|
||||||
// Board has RGB LED 21
|
// Board has RGB LED 21
|
||||||
#define HAS_NEOPIXEL // Enable the use of neopixels
|
#define HAS_NEOPIXEL // Enable the use of neopixels
|
||||||
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
|
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
|
||||||
|
|||||||
@@ -16,22 +16,12 @@
|
|||||||
#define SLEEP_TIME 120
|
#define SLEEP_TIME 120
|
||||||
|
|
||||||
#define GPS_DEFAULT_NOT_PRESENT 1
|
#define GPS_DEFAULT_NOT_PRESENT 1
|
||||||
// #define GPS_RX_PIN 44
|
|
||||||
// #define GPS_TX_PIN 43
|
|
||||||
|
|
||||||
// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
|
||||||
// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k)
|
|
||||||
// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage.
|
|
||||||
// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL
|
|
||||||
|
|
||||||
// keyboard
|
// keyboard
|
||||||
#define I2C_SDA 47 // I2C pins for this board
|
#define I2C_SDA 47 // I2C pins for this board
|
||||||
#define I2C_SCL 14
|
#define I2C_SCL 14
|
||||||
// #define KB_POWERON -1 // must be set to HIGH
|
|
||||||
// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55
|
|
||||||
// #define KB_BL_PIN 46 // not used for now
|
// #define KB_BL_PIN 46 // not used for now
|
||||||
#define KB_INT 13
|
#define KB_INT 13
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
|
|
||||||
#define TFT_DC 39
|
#define TFT_DC 39
|
||||||
#define TFT_CS 41
|
#define TFT_CS 41
|
||||||
@@ -44,11 +34,9 @@
|
|||||||
#define LORA_MOSI 3
|
#define LORA_MOSI 3
|
||||||
#define LORA_CS 17
|
#define LORA_CS 17
|
||||||
|
|
||||||
// #define LORA_DIO0 -1 // a No connect on the SX1262 module
|
|
||||||
#define LORA_RESET 18
|
#define LORA_RESET 18
|
||||||
#define LORA_DIO1 16 // SX1262 IRQ
|
#define LORA_DIO1 16 // SX1262 IRQ
|
||||||
#define LORA_DIO2 15 // SX1262 BUSY
|
#define LORA_DIO2 15 // SX1262 BUSY
|
||||||
// #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
|
|
||||||
|
|
||||||
#define SX126X_CS LORA_CS
|
#define SX126X_CS LORA_CS
|
||||||
#define SX126X_DIO1 LORA_DIO1
|
#define SX126X_DIO1 LORA_DIO1
|
||||||
@@ -58,4 +46,5 @@
|
|||||||
#define SX126X_DIO2_AS_RF_SWITCH
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
|
||||||
// #define LED_PIN 1
|
#define LED_NOTIFICATION 1
|
||||||
|
#define LED_STATE_ON 0
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
|
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
|
||||||
|
|
||||||
// Button A (44), B (43), R (12), U (13), L (11), D (18)
|
// Button A (44), B (43), R (12), U (13), L (11), D (18)
|
||||||
#define BUTTON_PIN 44 // If defined, this will be used for user button presses
|
#define BUTTON_PIN 43 // If defined, this will be used for user button presses
|
||||||
#define BUTTON_NEED_PULLUP
|
#define BUTTON_NEED_PULLUP
|
||||||
|
|
||||||
#define USE_RF95
|
#define USE_RF95
|
||||||
@@ -20,8 +20,19 @@
|
|||||||
#define LORA_MISO 7
|
#define LORA_MISO 7
|
||||||
#define LORA_MOSI 8
|
#define LORA_MOSI 8
|
||||||
#define LORA_CS 9
|
#define LORA_CS 9
|
||||||
#define LORA_DIO0 16 // a No connect on the SX1262 module
|
#define LORA_DIO0 16
|
||||||
#define LORA_RESET 4
|
#define LORA_RESET 4
|
||||||
|
|
||||||
#define LORA_DIO1 RADIOLIB_NC
|
#define LORA_DIO1 RADIOLIB_NC
|
||||||
#define LORA_DIO2 RADIOLIB_NC
|
#define LORA_DIO2 RADIOLIB_NC
|
||||||
|
|
||||||
|
// jk, its not really a trackball but we're gonna pretend!
|
||||||
|
#define HAS_TRACKBALL 1
|
||||||
|
#define TB_UP 13
|
||||||
|
#define TB_DOWN 18
|
||||||
|
#define TB_LEFT 11
|
||||||
|
#define TB_RIGHT 12
|
||||||
|
#define TB_PRESS 44 // BUTTON_PIN
|
||||||
|
#define TB_DIRECTION FALLING
|
||||||
|
|
||||||
|
#define ENABLE_AMBIENTLIGHTING
|
||||||
|
|||||||
@@ -52,8 +52,6 @@
|
|||||||
// Picomputer gets a white on black display
|
// Picomputer gets a white on black display
|
||||||
#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
|
#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
|
||||||
|
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
|
|
||||||
#define INPUTBROKER_MATRIX_TYPE 1
|
#define INPUTBROKER_MATRIX_TYPE 1
|
||||||
|
|
||||||
#define KEYS_COLS \
|
#define KEYS_COLS \
|
||||||
|
|||||||
@@ -22,9 +22,8 @@
|
|||||||
#define LED_BLUE 45
|
#define LED_BLUE 45
|
||||||
|
|
||||||
#define PIN_LED1 LED_GREEN
|
#define PIN_LED1 LED_GREEN
|
||||||
#define PIN_LED2 LED_BLUE
|
#define LED_NOTIFICATION LED_BLUE
|
||||||
|
|
||||||
#define LED_CONN LED_BLUE
|
|
||||||
#define LED_PIN LED_GREEN
|
#define LED_PIN LED_GREEN
|
||||||
#define ledOff(pin) pinMode(pin, INPUT)
|
#define ledOff(pin) pinMode(pin, INPUT)
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,8 @@
|
|||||||
#define LED_BLUE 45
|
#define LED_BLUE 45
|
||||||
|
|
||||||
#define PIN_LED1 LED_GREEN
|
#define PIN_LED1 LED_GREEN
|
||||||
#define PIN_LED2 LED_BLUE
|
#define LED_NOTIFICATION LED_BLUE
|
||||||
|
|
||||||
#define LED_CONN LED_BLUE
|
|
||||||
#define LED_PIN LED_GREEN
|
#define LED_PIN LED_GREEN
|
||||||
#define ledOff(pin) pinMode(pin, INPUT)
|
#define ledOff(pin) pinMode(pin, INPUT)
|
||||||
|
|
||||||
@@ -47,10 +46,8 @@
|
|||||||
#define SPI_MISO (10)
|
#define SPI_MISO (10)
|
||||||
#define SPI_CS (12)
|
#define SPI_CS (12)
|
||||||
|
|
||||||
#define HAS_BUTTON 1
|
|
||||||
#define BUTTON_PIN 0
|
#define BUTTON_PIN 0
|
||||||
|
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
#define USE_VIRTUAL_KEYBOARD 1
|
#define USE_VIRTUAL_KEYBOARD 1
|
||||||
|
|
||||||
#define BATTERY_PIN 1
|
#define BATTERY_PIN 1
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
|
|
||||||
// TCA8418 keyboard
|
// TCA8418 keyboard
|
||||||
#define KB_BL_PIN 42
|
#define KB_BL_PIN 42
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
|
|
||||||
// microphone PCM5102A
|
// microphone PCM5102A
|
||||||
#define PCM5102A_SCK 47
|
#define PCM5102A_SCK 47
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
#define KB_POWERON 10 // must be set to HIGH
|
#define KB_POWERON 10 // must be set to HIGH
|
||||||
#define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55
|
#define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55
|
||||||
#define KB_BL_PIN 46 // not used for now
|
#define KB_BL_PIN 46 // not used for now
|
||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
|
||||||
|
|
||||||
// trackball
|
// trackball
|
||||||
#define HAS_TRACKBALL 1
|
#define HAS_TRACKBALL 1
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user