mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-01 13:16:36 +00:00
Merge branch 'master' into T-beam-display-no-touch
This commit is contained in:
commit
01518a4387
@ -51,6 +51,7 @@ build_flags = -Wno-missing-field-initializers
|
|||||||
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
||||||
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||||
|
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||||
#-DBUILD_EPOCH=$UNIX_TIME
|
#-DBUILD_EPOCH=$UNIX_TIME
|
||||||
#-D OLED_PL=1
|
#-D OLED_PL=1
|
||||||
|
|
||||||
@ -103,12 +104,12 @@ lib_deps =
|
|||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
jgromes/RadioLib@7.1.2
|
jgromes/RadioLib@7.2.0
|
||||||
|
|
||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/d99edaf43775c9b235aab20521b034c99e04e4a8.zip
|
https://github.com/meshtastic/device-ui/archive/cdc6e5bdeedb8293d10e4a02be6ca64e95a7c515.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 6791138f0ba2b7c471072bd4bba6cbb8bacffe2d
|
Subproject commit 386fa53c1596c8dfc547521f08df107f4cb3a275
|
@ -26,7 +26,7 @@
|
|||||||
#ifndef SLEEP_TIME
|
#ifndef SLEEP_TIME
|
||||||
#define SLEEP_TIME 30
|
#define SLEEP_TIME 30
|
||||||
#endif
|
#endif
|
||||||
#if EXCLUDE_POWER_FSM
|
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
FakeFsm powerFSM;
|
FakeFsm powerFSM;
|
||||||
void PowerFSM_setup(){};
|
void PowerFSM_setup(){};
|
||||||
#else
|
#else
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep)
|
#define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep)
|
||||||
#define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen
|
#define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen
|
||||||
|
|
||||||
#if EXCLUDE_POWER_FSM
|
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
class FakeFsm
|
class FakeFsm
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -18,7 +18,7 @@ class PowerFSMThread : public OSThread
|
|||||||
protected:
|
protected:
|
||||||
int32_t runOnce() override
|
int32_t runOnce() override
|
||||||
{
|
{
|
||||||
#if !EXCLUDE_POWER_FSM
|
#if !MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
powerFSM.run_machine();
|
powerFSM.run_machine();
|
||||||
|
|
||||||
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
|
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
|
||||||
|
@ -352,8 +352,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
|||||||
for (uint16_t i = 0; i < len; i += 16) {
|
for (uint16_t i = 0; i < len; i += 16) {
|
||||||
if (i % 128 == 0)
|
if (i % 128 == 0)
|
||||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||||
char s[] = "| | | |\n";
|
char s[] = " | | | |\n";
|
||||||
uint8_t ix = 1, iy = 52;
|
uint8_t ix = 5, iy = 56;
|
||||||
for (uint8_t j = 0; j < 16; j++) {
|
for (uint8_t j = 0; j < 16; j++) {
|
||||||
if (i + j < len) {
|
if (i + j < len) {
|
||||||
uint8_t c = buf[i + j];
|
uint8_t c = buf[i + j];
|
||||||
@ -367,10 +367,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint8_t index = i / 16;
|
uint8_t index = i / 16;
|
||||||
if (i < 256)
|
sprintf(s, "%03x", index);
|
||||||
log(logLevel, " ");
|
s[3] = '.';
|
||||||
log(logLevel, "%02x", index);
|
|
||||||
log(logLevel, ".");
|
|
||||||
log(logLevel, s);
|
log(logLevel, s);
|
||||||
}
|
}
|
||||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||||
|
@ -58,7 +58,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "mesh/Channels.h"
|
#include "mesh/Channels.h"
|
||||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
#include "modules/AdminModule.h"
|
|
||||||
#include "modules/ExternalNotificationModule.h"
|
#include "modules/ExternalNotificationModule.h"
|
||||||
#include "modules/TextMessageModule.h"
|
#include "modules/TextMessageModule.h"
|
||||||
#include "modules/WaypointModule.h"
|
#include "modules/WaypointModule.h"
|
||||||
@ -1414,12 +1413,13 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg)
|
int Screen::handleAdminMessage(AdminModule_ObserverData *arg)
|
||||||
{
|
{
|
||||||
switch (arg->which_payload_variant) {
|
switch (arg->request->which_payload_variant) {
|
||||||
// Node removed manually (i.e. via app)
|
// Node removed manually (i.e. via app)
|
||||||
case meshtastic_AdminMessage_remove_by_nodenum_tag:
|
case meshtastic_AdminMessage_remove_by_nodenum_tag:
|
||||||
setFrames(FOCUS_PRESERVE);
|
setFrames(FOCUS_PRESERVE);
|
||||||
|
*arg->result = AdminMessageHandleResult::HANDLED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Default no-op, in case the admin message observable gets used by other classes in future
|
// Default no-op, in case the admin message observable gets used by other classes in future
|
||||||
|
@ -80,6 +80,7 @@ class Screen
|
|||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "input/InputBroker.h"
|
#include "input/InputBroker.h"
|
||||||
#include "mesh/MeshModule.h"
|
#include "mesh/MeshModule.h"
|
||||||
|
#include "modules/AdminModule.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -195,8 +196,8 @@ class Screen : public concurrency::OSThread
|
|||||||
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
|
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
|
||||||
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
||||||
CallbackObserver<Screen, const InputEvent *>(this, &Screen::handleInputEvent);
|
CallbackObserver<Screen, const InputEvent *>(this, &Screen::handleInputEvent);
|
||||||
CallbackObserver<Screen, const meshtastic_AdminMessage *> adminMessageObserver =
|
CallbackObserver<Screen, AdminModule_ObserverData *> adminMessageObserver =
|
||||||
CallbackObserver<Screen, const meshtastic_AdminMessage *>(this, &Screen::handleAdminMessage);
|
CallbackObserver<Screen, AdminModule_ObserverData *>(this, &Screen::handleAdminMessage);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
||||||
@ -546,7 +547,7 @@ class Screen : public concurrency::OSThread
|
|||||||
int handleTextMessage(const meshtastic_MeshPacket *arg);
|
int handleTextMessage(const meshtastic_MeshPacket *arg);
|
||||||
int handleUIFrameEvent(const UIFrameEvent *arg);
|
int handleUIFrameEvent(const UIFrameEvent *arg);
|
||||||
int handleInputEvent(const InputEvent *arg);
|
int handleInputEvent(const InputEvent *arg);
|
||||||
int handleAdminMessage(const meshtastic_AdminMessage *arg);
|
int handleAdminMessage(AdminModule_ObserverData *arg);
|
||||||
|
|
||||||
/// Used to force (super slow) eink displays to draw critical frames
|
/// Used to force (super slow) eink displays to draw critical frames
|
||||||
void forceDisplay(bool forceUiUpdate = false);
|
void forceDisplay(bool forceUiUpdate = false);
|
||||||
|
@ -19,6 +19,8 @@ namespace NicheGraphics::InkHUD
|
|||||||
enum MenuAction {
|
enum MenuAction {
|
||||||
NO_ACTION,
|
NO_ACTION,
|
||||||
SEND_PING,
|
SEND_PING,
|
||||||
|
STORE_CANNEDMESSAGE_SELECTION,
|
||||||
|
SEND_CANNEDMESSAGE,
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
NEXT_TILE,
|
NEXT_TILE,
|
||||||
TOGGLE_BACKLIGHT,
|
TOGGLE_BACKLIGHT,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
|
#include "Router.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
@ -31,6 +32,12 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
|
|||||||
if (settings->optionalMenuItems.backlight) {
|
if (settings->optionalMenuItems.backlight) {
|
||||||
backlight = Drivers::LatchingBacklight::getInstance();
|
backlight = Drivers::LatchingBacklight::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the Canned Message store
|
||||||
|
// This is a shared nicheGraphics component
|
||||||
|
// - handles loading & parsing the canned messages
|
||||||
|
// - handles setting / getting of canned messages via apps (Client API Admin Messages)
|
||||||
|
cm.store = CannedMessageStore::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onForeground()
|
void InkHUD::MenuApplet::onForeground()
|
||||||
@ -65,6 +72,10 @@ void InkHUD::MenuApplet::onForeground()
|
|||||||
|
|
||||||
void InkHUD::MenuApplet::onBackground()
|
void InkHUD::MenuApplet::onBackground()
|
||||||
{
|
{
|
||||||
|
// Discard any data we generated while selecting a canned message
|
||||||
|
// Frees heap mem
|
||||||
|
freeCannedMessageResources();
|
||||||
|
|
||||||
// If device has a backlight which isn't controlled by aux button:
|
// If device has a backlight which isn't controlled by aux button:
|
||||||
// Item in options submenu allows keeping backlight on after menu is closed
|
// Item in options submenu allows keeping backlight on after menu is closed
|
||||||
// If this item is deselected we will turn backlight off again, now that menu is closing
|
// If this item is deselected we will turn backlight off again, now that menu is closing
|
||||||
@ -153,6 +164,16 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case STORE_CANNEDMESSAGE_SELECTION:
|
||||||
|
cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SEND_CANNEDMESSAGE:
|
||||||
|
cm.selectedRecipientItem = &cm.recipientItems.at(cursor);
|
||||||
|
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
|
||||||
|
break;
|
||||||
|
|
||||||
case ROTATE:
|
case ROTATE:
|
||||||
inkhud->rotate();
|
inkhud->rotate();
|
||||||
break;
|
break;
|
||||||
@ -260,9 +281,11 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SEND:
|
case SEND:
|
||||||
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
|
populateSendPage();
|
||||||
// Todo: canned messages
|
break;
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
|
||||||
|
case CANNEDMESSAGE_RECIPIENT:
|
||||||
|
populateRecipientPage();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPTIONS:
|
case OPTIONS:
|
||||||
@ -497,6 +520,8 @@ void InkHUD::MenuApplet::populateAutoshowPage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create MenuItem entries to select our definition of "Recent"
|
||||||
|
// Controls how long data will remain in any "Recents" flavored applets
|
||||||
void InkHUD::MenuApplet::populateRecentsPage()
|
void InkHUD::MenuApplet::populateRecentsPage()
|
||||||
{
|
{
|
||||||
// How many values are shown for use to choose from
|
// How many values are shown for use to choose from
|
||||||
@ -510,6 +535,112 @@ void InkHUD::MenuApplet::populateRecentsPage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MenuItem entries for the "send" page
|
||||||
|
// Dynamically creates menu items based on available canned messages
|
||||||
|
void InkHUD::MenuApplet::populateSendPage()
|
||||||
|
{
|
||||||
|
// Position / NodeInfo packet
|
||||||
|
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
|
||||||
|
|
||||||
|
// One menu item for each canned message
|
||||||
|
uint8_t count = cm.store->size();
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
// Gather the information for this item
|
||||||
|
CannedMessages::MessageItem messageItem;
|
||||||
|
messageItem.rawText = cm.store->at(i);
|
||||||
|
messageItem.label = parse(messageItem.rawText);
|
||||||
|
|
||||||
|
// Store the item (until the menu closes)
|
||||||
|
cm.messageItems.push_back(messageItem);
|
||||||
|
|
||||||
|
// Create a menu item
|
||||||
|
const char *itemText = cm.messageItems.back().label.c_str();
|
||||||
|
items.push_back(MenuItem(itemText, MenuAction::STORE_CANNEDMESSAGE_SELECTION, MenuPage::CANNEDMESSAGE_RECIPIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamically create MenuItem entries for possible canned message destinations
|
||||||
|
// All available channels are shown
|
||||||
|
// Favorite nodes are shown, provided we don't have an *excessive* amount
|
||||||
|
void InkHUD::MenuApplet::populateRecipientPage()
|
||||||
|
{
|
||||||
|
// Create recipient data (and menu items) for any channels
|
||||||
|
// --------------------------------------------------------
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
||||||
|
// Get the channel, and check if it's enabled
|
||||||
|
meshtastic_Channel &channel = channels.getByIndex(i);
|
||||||
|
if (!channel.has_settings || channel.role == meshtastic_Channel_Role_DISABLED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CannedMessages::RecipientItem r;
|
||||||
|
|
||||||
|
// Set index
|
||||||
|
r.channelIndex = channel.index;
|
||||||
|
|
||||||
|
// Set a label for the menu item
|
||||||
|
r.label = "Ch " + to_string(i) + ": ";
|
||||||
|
if (channel.role == meshtastic_Channel_Role_PRIMARY)
|
||||||
|
r.label += "Primary";
|
||||||
|
else
|
||||||
|
r.label += parse(channel.settings.name);
|
||||||
|
|
||||||
|
// Add to the list of recipients
|
||||||
|
cm.recipientItems.push_back(r);
|
||||||
|
|
||||||
|
// Add a menu item for this recipient
|
||||||
|
const char *itemText = cm.recipientItems.back().label.c_str();
|
||||||
|
items.push_back(MenuItem(itemText, SEND_CANNEDMESSAGE, MenuPage::EXIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create recipient data (and menu items) for favorite nodes
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
uint32_t nodeCount = nodeDB->getNumMeshNodes();
|
||||||
|
uint32_t favoriteCount = 0;
|
||||||
|
|
||||||
|
// Count favorites
|
||||||
|
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||||
|
if (nodeDB->getMeshNodeByIndex(i)->is_favorite)
|
||||||
|
favoriteCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add favorites if the number is reasonable
|
||||||
|
// Don't want some monstrous list that takes 100 clicks to reach exit
|
||||||
|
if (favoriteCount < 20) {
|
||||||
|
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||||
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
|
// Skip node if not a favorite
|
||||||
|
if (!node->is_favorite)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CannedMessages::RecipientItem r;
|
||||||
|
|
||||||
|
r.dest = node->num;
|
||||||
|
r.channelIndex = nodeDB->getMeshNodeChannel(node->num); // Channel index only relevant if encrypted DM not possible(?)
|
||||||
|
|
||||||
|
// Set a label for the menu item
|
||||||
|
r.label = "DM: ";
|
||||||
|
if (node->has_user)
|
||||||
|
r.label += parse(node->user.long_name);
|
||||||
|
else
|
||||||
|
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
||||||
|
|
||||||
|
// Add to the list of recipients
|
||||||
|
cm.recipientItems.push_back(r);
|
||||||
|
|
||||||
|
// Add a menu item for this recipient
|
||||||
|
const char *itemText = cm.recipientItems.back().label.c_str();
|
||||||
|
items.push_back(MenuItem(itemText, SEND_CANNEDMESSAGE, MenuPage::EXIT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@ -619,4 +750,37 @@ uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a text message to the mesh
|
||||||
|
// Used to send our canned messages
|
||||||
|
void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char *message)
|
||||||
|
{
|
||||||
|
meshtastic_MeshPacket *p = router->allocForSending();
|
||||||
|
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||||
|
p->to = dest;
|
||||||
|
p->channel = channel;
|
||||||
|
p->want_ack = true;
|
||||||
|
p->decoded.payload.size = strlen(message);
|
||||||
|
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
|
||||||
|
|
||||||
|
// Tack on a bell character if requested
|
||||||
|
if (moduleConfig.canned_message.send_bell && p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) {
|
||||||
|
p->decoded.payload.bytes[p->decoded.payload.size] = 7; // Bell character
|
||||||
|
p->decoded.payload.bytes[p->decoded.payload.size + 1] = '\0'; // Append Null Terminator
|
||||||
|
p->decoded.payload.size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
|
||||||
|
|
||||||
|
service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free up any heap mmemory we'd used while selecting / sending canned messages
|
||||||
|
void InkHUD::MenuApplet::freeCannedMessageResources()
|
||||||
|
{
|
||||||
|
cm.selectedMessageItem = nullptr;
|
||||||
|
cm.selectedRecipientItem = nullptr;
|
||||||
|
cm.messageItems.clear();
|
||||||
|
cm.recipientItems.clear();
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -6,10 +6,12 @@
|
|||||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||||
#include "graphics/niche/InkHUD/Persistence.h"
|
#include "graphics/niche/InkHUD/Persistence.h"
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||||
|
#include "graphics/niche/Utils/CannedMessageStore.h"
|
||||||
|
|
||||||
#include "./MenuItem.h"
|
#include "./MenuItem.h"
|
||||||
#include "./MenuPage.h"
|
#include "./MenuPage.h"
|
||||||
|
|
||||||
|
#include "Channels.h"
|
||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
@ -36,12 +38,18 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
|
|
||||||
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
|
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
|
||||||
void showPage(MenuPage page); // Load and display a MenuPage
|
void showPage(MenuPage page); // Load and display a MenuPage
|
||||||
|
|
||||||
|
void populateSendPage(); // Dynamically create MenuItems including canned messages
|
||||||
|
void populateRecipientPage(); // Dynamically create a page of possible destinations for a canned message
|
||||||
void populateAppletPage(); // Dynamically create MenuItems for toggling loaded applets
|
void populateAppletPage(); // Dynamically create MenuItems for toggling loaded applets
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
|
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
||||||
|
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||||
|
|
||||||
MenuPage currentPage = MenuPage::ROOT;
|
MenuPage currentPage = MenuPage::ROOT;
|
||||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||||
@ -51,6 +59,37 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
|
|
||||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||||
|
|
||||||
|
// Data for selecting and sending canned messages via the menu
|
||||||
|
// Placed into a sub-class for organization only
|
||||||
|
class CannedMessages
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Share NicheGraphics component
|
||||||
|
// Handles loading, getting, setting
|
||||||
|
CannedMessageStore *store;
|
||||||
|
|
||||||
|
// One canned message
|
||||||
|
// Links the menu item to the true message text
|
||||||
|
struct MessageItem {
|
||||||
|
std::string label; // Shown in menu. Prefixed, and UTF-8 chars parsed
|
||||||
|
std::string rawText; // The message which will be sent, if this item is selected
|
||||||
|
} *selectedMessageItem;
|
||||||
|
|
||||||
|
// One possible destination for a canned message
|
||||||
|
// Links the menu item to the intended recipient
|
||||||
|
// May represent either broadcast or DM
|
||||||
|
struct RecipientItem {
|
||||||
|
std::string label; // Shown in menu
|
||||||
|
NodeNum dest = NODENUM_BROADCAST;
|
||||||
|
uint8_t channelIndex = 0;
|
||||||
|
} *selectedRecipientItem;
|
||||||
|
|
||||||
|
// These lists are generated when the menu page is populated
|
||||||
|
// Cleared onBackground (when MenuApplet closes)
|
||||||
|
std::vector<MessageItem> messageItems;
|
||||||
|
std::vector<RecipientItem> recipientItems;
|
||||||
|
} 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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
enum MenuPage : uint8_t {
|
enum MenuPage : uint8_t {
|
||||||
ROOT, // Initial menu page
|
ROOT, // Initial menu page
|
||||||
SEND,
|
SEND,
|
||||||
|
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
APPLETS,
|
APPLETS,
|
||||||
AUTOSHOW,
|
AUTOSHOW,
|
||||||
|
@ -13,7 +13,8 @@ using namespace NicheGraphics;
|
|||||||
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
|
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
|
||||||
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
|
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
|
||||||
|
|
||||||
InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) : channelIndex(channelIndex)
|
InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex)
|
||||||
|
: SinglePortModule("ThreadedMessageApplet", meshtastic_PortNum_TEXT_MESSAGE_APP), channelIndex(channelIndex)
|
||||||
{
|
{
|
||||||
// Create the message store
|
// Create the message store
|
||||||
// Will shortly attempt to load messages from RAM, if applet is active
|
// Will shortly attempt to load messages from RAM, if applet is active
|
||||||
@ -69,8 +70,7 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
|||||||
|
|
||||||
// Grab data for message
|
// Grab data for message
|
||||||
MessageStore::Message &m = store->messages.at(i);
|
MessageStore::Message &m = store->messages.at(i);
|
||||||
bool outgoing = (m.sender == 0);
|
bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
|
||||||
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
||||||
|
|
||||||
// Cache bottom Y of message text
|
// Cache bottom Y of message text
|
||||||
@ -171,54 +171,54 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
|||||||
void InkHUD::ThreadedMessageApplet::onActivate()
|
void InkHUD::ThreadedMessageApplet::onActivate()
|
||||||
{
|
{
|
||||||
loadMessagesFromFlash();
|
loadMessagesFromFlash();
|
||||||
textMessageObserver.observe(textMessageModule); // Begin handling any new text messages with onReceiveTextMessage
|
loopbackOk = true; // Allow us to handle messages generated on the node (canned messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code which runs when the applet stop running
|
// Code which runs when the applet stop running
|
||||||
// This might be happen at shutdown, or if user disables the applet at run-time
|
// This might be at shutdown, or if the user disables the applet at run-time, via the menu
|
||||||
void InkHUD::ThreadedMessageApplet::onDeactivate()
|
void InkHUD::ThreadedMessageApplet::onDeactivate()
|
||||||
{
|
{
|
||||||
textMessageObserver.unobserve(textMessageModule); // Stop handling any new text messages with onReceiveTextMessage
|
loopbackOk = false; // Slightly reduce our impact if the applet is disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new text messages
|
// Handle new text messages
|
||||||
// These might be incoming, from the mesh, or outgoing from phone
|
// These might be incoming, from the mesh, or outgoing from phone
|
||||||
// Each instance of the ThreadMessageApplet will only listen on one specific channel
|
// Each instance of the ThreadMessageApplet will only listen on one specific channel
|
||||||
// Method should return 0, to indicate general success to TextMessageModule
|
ProcessMessage InkHUD::ThreadedMessageApplet::handleReceived(const meshtastic_MeshPacket &mp)
|
||||||
int InkHUD::ThreadedMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
|
||||||
{
|
{
|
||||||
// Abort if applet fully deactivated
|
// Abort if applet fully deactivated
|
||||||
// Already handled by onActivate and onDeactivate, but good practice for all applets
|
|
||||||
if (!isActive())
|
if (!isActive())
|
||||||
return 0;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|
||||||
// Abort if wrong channel
|
// Abort if wrong channel
|
||||||
if (p->channel != this->channelIndex)
|
if (mp.channel != this->channelIndex)
|
||||||
return 0;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|
||||||
// Abort if message was a DM
|
// Abort if message was a DM
|
||||||
if (p->to != NODENUM_BROADCAST)
|
if (mp.to != NODENUM_BROADCAST)
|
||||||
return 0;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|
||||||
// Extract info into our slimmed-down "StoredMessage" type
|
// Extract info into our slimmed-down "StoredMessage" type
|
||||||
MessageStore::Message newMessage;
|
MessageStore::Message newMessage;
|
||||||
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
||||||
newMessage.sender = p->from;
|
newMessage.sender = mp.from;
|
||||||
newMessage.channelIndex = p->channel;
|
newMessage.channelIndex = mp.channel;
|
||||||
newMessage.text = std::string(&p->decoded.payload.bytes[0], &p->decoded.payload.bytes[p->decoded.payload.size]);
|
newMessage.text = std::string((const char *)mp.decoded.payload.bytes, mp.decoded.payload.size);
|
||||||
|
|
||||||
// Store newest message at front
|
// Store newest message at front
|
||||||
// These records are used when rendering, and also stored in flash at shutdown
|
// These records are used when rendering, and also stored in flash at shutdown
|
||||||
store->messages.push_front(newMessage);
|
store->messages.push_front(newMessage);
|
||||||
|
|
||||||
// If this was an incoming message, suggest that our applet becomes foreground, if permitted
|
// If this was an incoming message, suggest that our applet becomes foreground, if permitted
|
||||||
if (getFrom(p) != nodeDB->getNodeNum())
|
if (getFrom(&mp) != nodeDB->getNodeNum())
|
||||||
requestAutoshow();
|
requestAutoshow();
|
||||||
|
|
||||||
// Redraw the applet, perhaps.
|
// Redraw the applet, perhaps.
|
||||||
requestUpdate(); // Want to update display, if applet is foreground
|
requestUpdate(); // Want to update display, if applet is foreground
|
||||||
|
|
||||||
return 0;
|
// Tell Module API to continue informing other firmware components about this message
|
||||||
|
// We're not the only component which is interested in new text messages
|
||||||
|
return ProcessMessage::CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show notifications for text messages broadcast to our channel, when the applet is displayed
|
// Don't show notifications for text messages broadcast to our channel, when the applet is displayed
|
||||||
|
@ -30,7 +30,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
|
|
||||||
class Applet;
|
class Applet;
|
||||||
|
|
||||||
class ThreadedMessageApplet : public Applet
|
class ThreadedMessageApplet : public Applet, public SinglePortModule
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
||||||
@ -41,16 +41,11 @@ class ThreadedMessageApplet : public Applet
|
|||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
void onShutdown() override;
|
void onShutdown() override;
|
||||||
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
bool approveNotification(Notification &n) override; // Which notifications to suppress
|
bool approveNotification(Notification &n) override; // Which notifications to suppress
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Used to register our text message callback
|
|
||||||
CallbackObserver<ThreadedMessageApplet, const meshtastic_MeshPacket *> textMessageObserver =
|
|
||||||
CallbackObserver<ThreadedMessageApplet, const meshtastic_MeshPacket *>(this,
|
|
||||||
&ThreadedMessageApplet::onReceiveTextMessage);
|
|
||||||
|
|
||||||
void saveMessagesToFlash();
|
void saveMessagesToFlash();
|
||||||
void loadMessagesFromFlash();
|
void loadMessagesFromFlash();
|
||||||
|
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "buzz.h"
|
#include "buzz.h"
|
||||||
#include "modules/AdminModule.h"
|
|
||||||
#include "modules/ExternalNotificationModule.h"
|
#include "modules/ExternalNotificationModule.h"
|
||||||
#include "modules/TextMessageModule.h"
|
#include "modules/TextMessageModule.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
|
|
||||||
#include "./Applet.h"
|
#include "./Applet.h"
|
||||||
#include "./SystemApplet.h"
|
#include "./SystemApplet.h"
|
||||||
#include "graphics/niche/FlashData.h"
|
#include "graphics/niche/Utils/FlashData.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ void InkHUD::Events::begin()
|
|||||||
rebootObserver.observe(¬ifyReboot);
|
rebootObserver.observe(¬ifyReboot);
|
||||||
textMessageObserver.observe(textMessageModule);
|
textMessageObserver.observe(textMessageModule);
|
||||||
#if !MESHTASTIC_EXCLUDE_ADMIN
|
#if !MESHTASTIC_EXCLUDE_ADMIN
|
||||||
adminMessageObserver.observe(adminModule);
|
adminMessageObserver.observe((Observable<AdminModule_ObserverData *> *)adminModule);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
lightSleepObserver.observe(¬ifyLightSleep);
|
lightSleepObserver.observe(¬ifyLightSleep);
|
||||||
@ -193,14 +192,15 @@ int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
||||||
}
|
}
|
||||||
|
|
||||||
int InkHUD::Events::onAdminMessage(const meshtastic_AdminMessage *message)
|
int InkHUD::Events::onAdminMessage(AdminModule_ObserverData *data)
|
||||||
{
|
{
|
||||||
switch (message->which_payload_variant) {
|
switch (data->request->which_payload_variant) {
|
||||||
// Factory reset
|
// Factory reset
|
||||||
// Two possible messages. One preserves BLE bonds, other wipes. Both should clear InkHUD data.
|
// Two possible messages. One preserves BLE bonds, other wipes. Both should clear InkHUD data.
|
||||||
case meshtastic_AdminMessage_factory_reset_device_tag:
|
case meshtastic_AdminMessage_factory_reset_device_tag:
|
||||||
case meshtastic_AdminMessage_factory_reset_config_tag:
|
case meshtastic_AdminMessage_factory_reset_config_tag:
|
||||||
eraseOnReboot = true;
|
eraseOnReboot = true;
|
||||||
|
*data->result = AdminMessageHandleResult::HANDLED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -13,7 +13,7 @@ however this class handles general events which concern InkHUD as a whole, e.g.
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "Observer.h"
|
#include "modules/AdminModule.h"
|
||||||
|
|
||||||
#include "./InkHUD.h"
|
#include "./InkHUD.h"
|
||||||
#include "./Persistence.h"
|
#include "./Persistence.h"
|
||||||
@ -33,7 +33,7 @@ class Events
|
|||||||
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
|
||||||
int onAdminMessage(const meshtastic_AdminMessage *message); // Handle incoming admin messages
|
int onAdminMessage(AdminModule_ObserverData *data); // Handle incoming admin messages
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
int beforeLightSleep(void *unused); // Prepare for light sleep
|
int beforeLightSleep(void *unused); // Prepare for light sleep
|
||||||
#endif
|
#endif
|
||||||
@ -54,8 +54,8 @@ class Events
|
|||||||
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
|
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
|
||||||
|
|
||||||
// Get notified of incoming admin messages, and handle any which are relevant to InkHUD
|
// Get notified of incoming admin messages, and handle any which are relevant to InkHUD
|
||||||
CallbackObserver<Events, const meshtastic_AdminMessage *> adminMessageObserver =
|
CallbackObserver<Events, AdminModule_ObserverData *> adminMessageObserver =
|
||||||
CallbackObserver<Events, const meshtastic_AdminMessage *>(this, &Events::onAdminMessage);
|
CallbackObserver<Events, AdminModule_ObserverData *>(this, &Events::onAdminMessage);
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
// Get notified when the system is entering light sleep
|
// Get notified when the system is entering light sleep
|
||||||
|
@ -15,8 +15,8 @@ The save / load mechanism is a shared NicheGraphics feature.
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "./InkHUD.h"
|
#include "./InkHUD.h"
|
||||||
#include "graphics/niche/FlashData.h"
|
|
||||||
#include "graphics/niche/InkHUD/MessageStore.h"
|
#include "graphics/niche/InkHUD/MessageStore.h"
|
||||||
|
#include "graphics/niche/Utils/FlashData.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
163
src/graphics/niche/Utils/CannedMessageStore.cpp
Normal file
163
src/graphics/niche/Utils/CannedMessageStore.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
#include "./CannedMessageStore.h"
|
||||||
|
|
||||||
|
#include "FSCommon.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "SPILock.h"
|
||||||
|
#include "generated/meshtastic/cannedmessages.pb.h"
|
||||||
|
|
||||||
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
|
// Location of the file which stores the canned messages on flash
|
||||||
|
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
|
||||||
|
|
||||||
|
CannedMessageStore::CannedMessageStore()
|
||||||
|
{
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ADMIN
|
||||||
|
adminMessageObserver.observe(adminModule);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Load & parse messages from flash
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get access to (or create) the singleton instance of this class
|
||||||
|
CannedMessageStore *CannedMessageStore::getInstance()
|
||||||
|
{
|
||||||
|
// Instantiate the class the first time this method is called
|
||||||
|
static CannedMessageStore *const singletonInstance = new CannedMessageStore;
|
||||||
|
|
||||||
|
return singletonInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access canned messages by index
|
||||||
|
// Consumer should check CannedMessageStore::size to avoid accessing out of bounds
|
||||||
|
const std::string &CannedMessageStore::at(uint8_t i)
|
||||||
|
{
|
||||||
|
assert(i < messages.size());
|
||||||
|
return messages.at(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of canned message strings available
|
||||||
|
uint8_t CannedMessageStore::size()
|
||||||
|
{
|
||||||
|
return messages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load canned message data from flash, and parse into the individual strings
|
||||||
|
void CannedMessageStore::load()
|
||||||
|
{
|
||||||
|
// In case we're reloading
|
||||||
|
messages.clear();
|
||||||
|
|
||||||
|
// Attempt to load the bulk canned message data from flash
|
||||||
|
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
|
||||||
|
LoadFileResult result = nodeDB->loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
|
||||||
|
sizeof(meshtastic_CannedMessageModuleConfig),
|
||||||
|
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||||
|
|
||||||
|
// Abort if nothing to load
|
||||||
|
if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Split into individual canned messages
|
||||||
|
// These are concatenated when stored in flash, using '|' as a delimiter
|
||||||
|
std::string s;
|
||||||
|
for (char c : cannedMessageModuleConfig.messages) { // Character by character
|
||||||
|
|
||||||
|
// If found end of a string
|
||||||
|
if (c == '|' || c == '\0') {
|
||||||
|
// Copy into the vector (if non-empty)
|
||||||
|
if (!s.empty())
|
||||||
|
messages.push_back(s);
|
||||||
|
|
||||||
|
// Reset the string builder
|
||||||
|
s.clear();
|
||||||
|
|
||||||
|
// End of data, all strings processed
|
||||||
|
if (c == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, append char (continue building string)
|
||||||
|
else
|
||||||
|
s.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle incoming admin messages
|
||||||
|
// We get these as an observer of AdminModule
|
||||||
|
// It's our responsibility to handle setting and getting of canned messages via the client API
|
||||||
|
// Ordinarily, this would be handled by the CannedMessageModule, but it is bound to Screen.cpp, so not suitable for NicheGraphics
|
||||||
|
int CannedMessageStore::onAdminMessage(AdminModule_ObserverData *data)
|
||||||
|
{
|
||||||
|
switch (data->request->which_payload_variant) {
|
||||||
|
|
||||||
|
// Client API changing the canned messages
|
||||||
|
case meshtastic_AdminMessage_set_canned_message_module_messages_tag:
|
||||||
|
handleSet(data->request);
|
||||||
|
*data->result = AdminMessageHandleResult::HANDLED;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Client API wants to know the current canned messages
|
||||||
|
case meshtastic_AdminMessage_get_canned_message_module_messages_request_tag:
|
||||||
|
handleGet(data->response);
|
||||||
|
*data->result = AdminMessageHandleResult::HANDLED_WITH_RESPONSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client API changing the canned messages
|
||||||
|
void CannedMessageStore::handleSet(const meshtastic_AdminMessage *request)
|
||||||
|
{
|
||||||
|
// Copy into the correct struct (for writing to flash as protobuf)
|
||||||
|
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
|
||||||
|
strncpy(cannedMessageModuleConfig.messages, request->set_canned_message_module_messages,
|
||||||
|
sizeof(cannedMessageModuleConfig.messages));
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
#ifdef FSCom
|
||||||
|
spiLock->lock();
|
||||||
|
FSCom.mkdir("/prefs");
|
||||||
|
spiLock->unlock();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Write to flash
|
||||||
|
nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||||
|
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||||
|
|
||||||
|
// Reload from flash, to update the canned messages in RAM
|
||||||
|
// (This is a lazy way to handle it)
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client API wants to know the current canned messages
|
||||||
|
// We're reconstructing the monolithic canned message string from our copy of the messages in RAM
|
||||||
|
// Lazy, but more convenient that reloading the monolithic string from flash just for this
|
||||||
|
void CannedMessageStore::handleGet(meshtastic_AdminMessage *response)
|
||||||
|
{
|
||||||
|
// Merge the canned messages back into the delimited format expected
|
||||||
|
std::string merged;
|
||||||
|
if (!messages.empty()) { // Don't run if no messages: error on pop_back with size=0
|
||||||
|
merged.reserve(201);
|
||||||
|
for (std::string &s : messages) {
|
||||||
|
merged += s;
|
||||||
|
merged += '|';
|
||||||
|
}
|
||||||
|
merged.pop_back(); // Drop the final delimiter (loop added one too many)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place the data into the response
|
||||||
|
// This response is scoped to AdminModule::handleReceivedProtobuf
|
||||||
|
// We were passed reference to it via the observable
|
||||||
|
response->which_payload_variant = meshtastic_AdminMessage_get_canned_message_module_messages_response_tag;
|
||||||
|
strncpy(response->get_canned_message_module_messages_response, merged.c_str(), strlen(merged.c_str()) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
54
src/graphics/niche/Utils/CannedMessageStore.h
Normal file
54
src/graphics/niche/Utils/CannedMessageStore.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Re-usable NicheGraphics tool
|
||||||
|
|
||||||
|
Makes canned message data accessible to any NicheGraphics UI.
|
||||||
|
- handles loading & parsing from flash
|
||||||
|
- handles the admin messages for setting & getting canned messages via client API (phone apps, etc)
|
||||||
|
|
||||||
|
The original CannedMessageModule class is bound to Screen.cpp,
|
||||||
|
making it incompatible with the NicheGraphics framework, which suppresses Screen.cpp
|
||||||
|
|
||||||
|
This implementation aims to be self-contained.
|
||||||
|
The necessary interaction with the AdminModule is done as an observer.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include "modules/AdminModule.h"
|
||||||
|
|
||||||
|
namespace NicheGraphics
|
||||||
|
{
|
||||||
|
|
||||||
|
class CannedMessageStore
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static CannedMessageStore *getInstance(); // Create or get the singleton instance
|
||||||
|
const std::string &at(uint8_t i); // Get canned message at index
|
||||||
|
uint8_t size(); // Get total number of canned messages
|
||||||
|
|
||||||
|
int onAdminMessage(AdminModule_ObserverData *data); // Handle incoming admin messages
|
||||||
|
|
||||||
|
private:
|
||||||
|
CannedMessageStore(); // Constructor made private: force use of CannedMessageStore::instance()
|
||||||
|
|
||||||
|
void load(); // Load from flash, and parse
|
||||||
|
|
||||||
|
void handleSet(const meshtastic_AdminMessage *request); // Client API changing the canned messages
|
||||||
|
void handleGet(meshtastic_AdminMessage *response); // Client API wants to know current canned messages
|
||||||
|
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
|
||||||
|
// Get notified of incoming admin messages, to get / set canned messages
|
||||||
|
CallbackObserver<CannedMessageStore, AdminModule_ObserverData *> adminMessageObserver =
|
||||||
|
CallbackObserver<CannedMessageStore, AdminModule_ObserverData *>(this, &CannedMessageStore::onAdminMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace NicheGraphics
|
||||||
|
|
||||||
|
#endif
|
@ -12,6 +12,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
|
|||||||
this->_pinUp = pinUp;
|
this->_pinUp = pinUp;
|
||||||
this->_pinLeft = pinLeft;
|
this->_pinLeft = pinLeft;
|
||||||
this->_pinRight = pinRight;
|
this->_pinRight = pinRight;
|
||||||
|
this->_pinPress = pinPress;
|
||||||
this->_eventDown = eventDown;
|
this->_eventDown = eventDown;
|
||||||
this->_eventUp = eventUp;
|
this->_eventUp = eventUp;
|
||||||
this->_eventLeft = eventLeft;
|
this->_eventLeft = eventLeft;
|
||||||
@ -20,23 +21,23 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
|
|||||||
|
|
||||||
if (pinPress != 255) {
|
if (pinPress != 255) {
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
attachInterrupt(pinPress, onIntPress, RISING);
|
attachInterrupt(pinPress, onIntPress, TB_DIRECTION);
|
||||||
}
|
}
|
||||||
if (this->_pinDown != 255) {
|
if (this->_pinDown != 255) {
|
||||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
attachInterrupt(this->_pinDown, onIntDown, TB_DIRECTION);
|
||||||
}
|
}
|
||||||
if (this->_pinUp != 255) {
|
if (this->_pinUp != 255) {
|
||||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
attachInterrupt(this->_pinUp, onIntUp, TB_DIRECTION);
|
||||||
}
|
}
|
||||||
if (this->_pinLeft != 255) {
|
if (this->_pinLeft != 255) {
|
||||||
pinMode(this->_pinLeft, INPUT_PULLUP);
|
pinMode(this->_pinLeft, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinLeft, onIntLeft, RISING);
|
attachInterrupt(this->_pinLeft, onIntLeft, TB_DIRECTION);
|
||||||
}
|
}
|
||||||
if (this->_pinRight != 255) {
|
if (this->_pinRight != 255) {
|
||||||
pinMode(this->_pinRight, INPUT_PULLUP);
|
pinMode(this->_pinRight, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinRight, onIntRight, RISING);
|
attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
|
LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
|
||||||
@ -67,19 +68,19 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
e.inputEvent = this->_eventRight;
|
e.inputEvent = this->_eventRight;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (this->action == TB_ACTION_PRESSED) {
|
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) {
|
||||||
// LOG_DEBUG("Trackball event Press");
|
// LOG_DEBUG("Trackball event Press");
|
||||||
e.inputEvent = this->_eventPressed;
|
e.inputEvent = this->_eventPressed;
|
||||||
} else if (this->action == TB_ACTION_UP) {
|
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) {
|
||||||
// LOG_DEBUG("Trackball event UP");
|
// LOG_DEBUG("Trackball event UP");
|
||||||
e.inputEvent = this->_eventUp;
|
e.inputEvent = this->_eventUp;
|
||||||
} else if (this->action == TB_ACTION_DOWN) {
|
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown)) {
|
||||||
// LOG_DEBUG("Trackball event DOWN");
|
// LOG_DEBUG("Trackball event DOWN");
|
||||||
e.inputEvent = this->_eventDown;
|
e.inputEvent = this->_eventDown;
|
||||||
} else if (this->action == TB_ACTION_LEFT) {
|
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft)) {
|
||||||
// LOG_DEBUG("Trackball event LEFT");
|
// LOG_DEBUG("Trackball event LEFT");
|
||||||
e.inputEvent = this->_eventLeft;
|
e.inputEvent = this->_eventLeft;
|
||||||
} else if (this->action == TB_ACTION_RIGHT) {
|
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight)) {
|
||||||
// LOG_DEBUG("Trackball event RIGHT");
|
// LOG_DEBUG("Trackball event RIGHT");
|
||||||
e.inputEvent = this->_eventRight;
|
e.inputEvent = this->_eventRight;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
#include "mesh/NodeDB.h"
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
|
#ifndef TB_DIRECTION
|
||||||
|
#define TB_DIRECTION RISING
|
||||||
|
#endif
|
||||||
|
|
||||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -16,6 +20,7 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
void intUpHandler();
|
void intUpHandler();
|
||||||
void intLeftHandler();
|
void intLeftHandler();
|
||||||
void intRightHandler();
|
void intRightHandler();
|
||||||
|
uint32_t lastTime = 0;
|
||||||
|
|
||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
@ -28,14 +33,15 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
TB_ACTION_LEFT,
|
TB_ACTION_LEFT,
|
||||||
TB_ACTION_RIGHT
|
TB_ACTION_RIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t _pinDown = 0;
|
uint8_t _pinDown = 0;
|
||||||
uint8_t _pinUp = 0;
|
uint8_t _pinUp = 0;
|
||||||
uint8_t _pinLeft = 0;
|
uint8_t _pinLeft = 0;
|
||||||
uint8_t _pinRight = 0;
|
uint8_t _pinRight = 0;
|
||||||
|
uint8_t _pinPress = 0;
|
||||||
|
|
||||||
|
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
||||||
|
|
||||||
|
private:
|
||||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
||||||
|
@ -23,21 +23,41 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
|
|||||||
|
|
||||||
void TrackballInterruptImpl1::handleIntDown()
|
void TrackballInterruptImpl1::handleIntDown()
|
||||||
{
|
{
|
||||||
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
trackballInterruptImpl1->intDownHandler();
|
trackballInterruptImpl1->intDownHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntUp()
|
void TrackballInterruptImpl1::handleIntUp()
|
||||||
{
|
{
|
||||||
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
trackballInterruptImpl1->intUpHandler();
|
trackballInterruptImpl1->intUpHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntLeft()
|
void TrackballInterruptImpl1::handleIntLeft()
|
||||||
{
|
{
|
||||||
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
trackballInterruptImpl1->intLeftHandler();
|
trackballInterruptImpl1->intLeftHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntRight()
|
void TrackballInterruptImpl1::handleIntRight()
|
||||||
{
|
{
|
||||||
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
trackballInterruptImpl1->intRightHandler();
|
trackballInterruptImpl1->intRightHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntPressed()
|
void TrackballInterruptImpl1::handleIntPressed()
|
||||||
{
|
{
|
||||||
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
trackballInterruptImpl1->intPressHandler();
|
trackballInterruptImpl1->intPressHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1422,7 +1422,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
|
|||||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AUDIO_CONFIG;
|
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AUDIO_CONFIG;
|
||||||
#endif
|
#endif
|
||||||
// Option to explicitly include canned messages for edge cases, e.g. niche graphics
|
// Option to explicitly include canned messages for edge cases, e.g. niche graphics
|
||||||
#if (!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES
|
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
||||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
|
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
|
||||||
#endif
|
#endif
|
||||||
#if NO_EXT_GPIO
|
#if NO_EXT_GPIO
|
||||||
|
@ -61,12 +61,17 @@ class Default
|
|||||||
throttlingFactor = 0.04;
|
throttlingFactor = 0.04;
|
||||||
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
|
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
|
||||||
throttlingFactor = 0.02;
|
throttlingFactor = 0.02;
|
||||||
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW)
|
|
||||||
throttlingFactor = 0.01;
|
|
||||||
else if (config.lora.use_preset &&
|
else if (config.lora.use_preset &&
|
||||||
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
||||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO))
|
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
|
||||||
return 1.0; // Don't bother throttling for highest bandwidth presets
|
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW))
|
||||||
|
throttlingFactor = 0.01;
|
||||||
|
|
||||||
|
#if USERPREFS_EVENT_MODE
|
||||||
|
// If we are in event mode, scale down the throttling factor
|
||||||
|
throttlingFactor = 0.04;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Scaling up traffic based on number of nodes over 40
|
// Scaling up traffic based on number of nodes over 40
|
||||||
int nodesOverForty = (numOnlineNodes - 40);
|
int nodesOverForty = (numOnlineNodes - 40);
|
||||||
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
|
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
|
||||||
|
@ -91,7 +91,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
|||||||
/* MAX17261 lipo battery gauge */
|
/* MAX17261 lipo battery gauge */
|
||||||
meshtastic_TelemetrySensorType_MAX17261 = 38,
|
meshtastic_TelemetrySensorType_MAX17261 = 38,
|
||||||
/* PCT2075 Temperature Sensor */
|
/* PCT2075 Temperature Sensor */
|
||||||
meshtastic_TelemetrySensorType_PCT2075 = 39
|
meshtastic_TelemetrySensorType_PCT2075 = 39,
|
||||||
|
/* ADS1X15 ADC */
|
||||||
|
meshtastic_TelemetrySensorType_ADS1X15 = 40
|
||||||
} meshtastic_TelemetrySensorType;
|
} meshtastic_TelemetrySensorType;
|
||||||
|
|
||||||
/* Struct definitions */
|
/* Struct definitions */
|
||||||
@ -206,6 +208,36 @@ typedef struct _meshtastic_PowerMetrics {
|
|||||||
/* Current (Ch3) */
|
/* Current (Ch3) */
|
||||||
bool has_ch3_current;
|
bool has_ch3_current;
|
||||||
float ch3_current;
|
float ch3_current;
|
||||||
|
/* Voltage (Ch4) */
|
||||||
|
bool has_ch4_voltage;
|
||||||
|
float ch4_voltage;
|
||||||
|
/* Current (Ch4) */
|
||||||
|
bool has_ch4_current;
|
||||||
|
float ch4_current;
|
||||||
|
/* Voltage (Ch5) */
|
||||||
|
bool has_ch5_voltage;
|
||||||
|
float ch5_voltage;
|
||||||
|
/* Current (Ch5) */
|
||||||
|
bool has_ch5_current;
|
||||||
|
float ch5_current;
|
||||||
|
/* Voltage (Ch6) */
|
||||||
|
bool has_ch6_voltage;
|
||||||
|
float ch6_voltage;
|
||||||
|
/* Current (Ch6) */
|
||||||
|
bool has_ch6_current;
|
||||||
|
float ch6_current;
|
||||||
|
/* Voltage (Ch7) */
|
||||||
|
bool has_ch7_voltage;
|
||||||
|
float ch7_voltage;
|
||||||
|
/* Current (Ch7) */
|
||||||
|
bool has_ch7_current;
|
||||||
|
float ch7_current;
|
||||||
|
/* Voltage (Ch8) */
|
||||||
|
bool has_ch8_voltage;
|
||||||
|
float ch8_voltage;
|
||||||
|
/* Current (Ch8) */
|
||||||
|
bool has_ch8_current;
|
||||||
|
float ch8_current;
|
||||||
} meshtastic_PowerMetrics;
|
} meshtastic_PowerMetrics;
|
||||||
|
|
||||||
/* Air quality metrics */
|
/* Air quality metrics */
|
||||||
@ -360,8 +392,8 @@ extern "C" {
|
|||||||
|
|
||||||
/* Helper constants for enums */
|
/* Helper constants for enums */
|
||||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_PCT2075
|
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_ADS1X15
|
||||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_PCT2075+1))
|
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_ADS1X15+1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -376,7 +408,7 @@ extern "C" {
|
|||||||
/* Initializer values for message structs */
|
/* Initializer values for message structs */
|
||||||
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
|
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
|
||||||
@ -385,7 +417,7 @@ extern "C" {
|
|||||||
#define meshtastic_Nau7802Config_init_default {0, 0}
|
#define meshtastic_Nau7802Config_init_default {0, 0}
|
||||||
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||||
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
|
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
|
||||||
@ -427,6 +459,16 @@ extern "C" {
|
|||||||
#define meshtastic_PowerMetrics_ch2_current_tag 4
|
#define meshtastic_PowerMetrics_ch2_current_tag 4
|
||||||
#define meshtastic_PowerMetrics_ch3_voltage_tag 5
|
#define meshtastic_PowerMetrics_ch3_voltage_tag 5
|
||||||
#define meshtastic_PowerMetrics_ch3_current_tag 6
|
#define meshtastic_PowerMetrics_ch3_current_tag 6
|
||||||
|
#define meshtastic_PowerMetrics_ch4_voltage_tag 7
|
||||||
|
#define meshtastic_PowerMetrics_ch4_current_tag 8
|
||||||
|
#define meshtastic_PowerMetrics_ch5_voltage_tag 9
|
||||||
|
#define meshtastic_PowerMetrics_ch5_current_tag 10
|
||||||
|
#define meshtastic_PowerMetrics_ch6_voltage_tag 11
|
||||||
|
#define meshtastic_PowerMetrics_ch6_current_tag 12
|
||||||
|
#define meshtastic_PowerMetrics_ch7_voltage_tag 13
|
||||||
|
#define meshtastic_PowerMetrics_ch7_current_tag 14
|
||||||
|
#define meshtastic_PowerMetrics_ch8_voltage_tag 15
|
||||||
|
#define meshtastic_PowerMetrics_ch8_current_tag 16
|
||||||
#define meshtastic_AirQualityMetrics_pm10_standard_tag 1
|
#define meshtastic_AirQualityMetrics_pm10_standard_tag 1
|
||||||
#define meshtastic_AirQualityMetrics_pm25_standard_tag 2
|
#define meshtastic_AirQualityMetrics_pm25_standard_tag 2
|
||||||
#define meshtastic_AirQualityMetrics_pm100_standard_tag 3
|
#define meshtastic_AirQualityMetrics_pm100_standard_tag 3
|
||||||
@ -518,7 +560,17 @@ X(a, STATIC, OPTIONAL, FLOAT, ch1_current, 2) \
|
|||||||
X(a, STATIC, OPTIONAL, FLOAT, ch2_voltage, 3) \
|
X(a, STATIC, OPTIONAL, FLOAT, ch2_voltage, 3) \
|
||||||
X(a, STATIC, OPTIONAL, FLOAT, ch2_current, 4) \
|
X(a, STATIC, OPTIONAL, FLOAT, ch2_current, 4) \
|
||||||
X(a, STATIC, OPTIONAL, FLOAT, ch3_voltage, 5) \
|
X(a, STATIC, OPTIONAL, FLOAT, ch3_voltage, 5) \
|
||||||
X(a, STATIC, OPTIONAL, FLOAT, ch3_current, 6)
|
X(a, STATIC, OPTIONAL, FLOAT, ch3_current, 6) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch4_voltage, 7) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch4_current, 8) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch5_voltage, 9) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch5_current, 10) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch6_voltage, 11) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch6_current, 12) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch7_voltage, 13) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch7_current, 14) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch8_voltage, 15) \
|
||||||
|
X(a, STATIC, OPTIONAL, FLOAT, ch8_current, 16)
|
||||||
#define meshtastic_PowerMetrics_CALLBACK NULL
|
#define meshtastic_PowerMetrics_CALLBACK NULL
|
||||||
#define meshtastic_PowerMetrics_DEFAULT NULL
|
#define meshtastic_PowerMetrics_DEFAULT NULL
|
||||||
|
|
||||||
@ -631,7 +683,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
|||||||
#define meshtastic_HostMetrics_size 264
|
#define meshtastic_HostMetrics_size 264
|
||||||
#define meshtastic_LocalStats_size 72
|
#define meshtastic_LocalStats_size 72
|
||||||
#define meshtastic_Nau7802Config_size 16
|
#define meshtastic_Nau7802Config_size 16
|
||||||
#define meshtastic_PowerMetrics_size 30
|
#define meshtastic_PowerMetrics_size 81
|
||||||
#define meshtastic_Telemetry_size 272
|
#define meshtastic_Telemetry_size 272
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -470,22 +470,38 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
setPassKey(&res);
|
setPassKey(&res);
|
||||||
myReply = allocDataProtobuf(res);
|
myReply = allocDataProtobuf(res);
|
||||||
} else if (mp.decoded.want_response) {
|
} else if (mp.decoded.want_response) {
|
||||||
LOG_DEBUG("Did not responded to a request that wanted a respond. req.variant=%d", r->which_payload_variant);
|
LOG_DEBUG("Module API did not respond to admin message. req.variant=%d", r->which_payload_variant);
|
||||||
} else if (handleResult != AdminMessageHandleResult::HANDLED) {
|
} else if (handleResult != AdminMessageHandleResult::HANDLED) {
|
||||||
// Probably a message sent by us or sent to our local node. FIXME, we should avoid scanning these messages
|
// Probably a message sent by us or sent to our local node. FIXME, we should avoid scanning these messages
|
||||||
LOG_DEBUG("Ignore irrelevant admin %d", r->which_payload_variant);
|
LOG_DEBUG("Module API did not handle admin message %d", r->which_payload_variant);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow any observers (e.g. the UI) to handle/respond
|
||||||
|
AdminMessageHandleResult observerResult = AdminMessageHandleResult::NOT_HANDLED;
|
||||||
|
meshtastic_AdminMessage observerResponse = meshtastic_AdminMessage_init_default;
|
||||||
|
AdminModule_ObserverData observerData = {
|
||||||
|
.request = r,
|
||||||
|
.response = &observerResponse,
|
||||||
|
.result = &observerResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
notifyObservers(&observerData);
|
||||||
|
|
||||||
|
if (observerResult == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) {
|
||||||
|
setPassKey(&observerResponse);
|
||||||
|
myReply = allocDataProtobuf(observerResponse);
|
||||||
|
LOG_DEBUG("Observer responded to admin message");
|
||||||
|
} else if (observerResult == AdminMessageHandleResult::HANDLED) {
|
||||||
|
LOG_DEBUG("Observer handled admin message");
|
||||||
|
}
|
||||||
|
|
||||||
// If asked for a response and it is not yet set, generate an 'ACK' response
|
// If asked for a response and it is not yet set, generate an 'ACK' response
|
||||||
if (mp.decoded.want_response && !myReply) {
|
if (mp.decoded.want_response && !myReply) {
|
||||||
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
|
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow any observers (e.g. the UI) to respond to this event
|
|
||||||
notifyObservers(r);
|
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1137,7 +1153,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
conn.has_serial = true; // No serial-less devices
|
conn.has_serial = true; // No serial-less devices
|
||||||
#if !EXCLUDE_POWER_FSM
|
#if !MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
conn.serial.is_connected = powerFSM.getState() == &stateSERIAL;
|
conn.serial.is_connected = powerFSM.getState() == &stateSERIAL;
|
||||||
#else
|
#else
|
||||||
conn.serial.is_connected = powerFSM.getState();
|
conn.serial.is_connected = powerFSM.getState();
|
||||||
|
@ -6,10 +6,19 @@
|
|||||||
#include "mesh/wifi/WiFiAPClient.h"
|
#include "mesh/wifi/WiFiAPClient.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Datatype passed to Observers by AdminModule, to allow external handling of admin messages
|
||||||
|
*/
|
||||||
|
struct AdminModule_ObserverData {
|
||||||
|
const meshtastic_AdminMessage *request;
|
||||||
|
meshtastic_AdminMessage *response;
|
||||||
|
AdminMessageHandleResult *result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin module for admin messages
|
* Admin module for admin messages
|
||||||
*/
|
*/
|
||||||
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Observable<const meshtastic_AdminMessage *>
|
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Observable<AdminModule_ObserverData *>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/** Constructor
|
/** Constructor
|
||||||
|
@ -32,7 +32,6 @@ build_flags =
|
|||||||
${inkhud.build_flags}
|
${inkhud.build_flags}
|
||||||
-I variants/heltec_vision_master_e213
|
-I variants/heltec_vision_master_e213
|
||||||
-D HELTEC_VISION_MASTER_E213
|
-D HELTEC_VISION_MASTER_E213
|
||||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
|
@ -36,7 +36,6 @@ build_flags =
|
|||||||
${inkhud.build_flags}
|
${inkhud.build_flags}
|
||||||
-I variants/heltec_vision_master_e290
|
-I variants/heltec_vision_master_e290
|
||||||
-D HELTEC_VISION_MASTER_E290
|
-D HELTEC_VISION_MASTER_E290
|
||||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
|
@ -33,7 +33,6 @@ build_flags =
|
|||||||
${inkhud.build_flags}
|
${inkhud.build_flags}
|
||||||
-I variants/heltec_wireless_paper
|
-I variants/heltec_wireless_paper
|
||||||
-D HELTEC_WIRELESS_PAPER
|
-D HELTEC_WIRELESS_PAPER
|
||||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
|
@ -28,7 +28,6 @@ build_flags = ${esp32s3_base.build_flags}
|
|||||||
-D USE_LOG_DEBUG
|
-D USE_LOG_DEBUG
|
||||||
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
|
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
|
||||||
-D RADIOLIB_SPI_PARANOID=0
|
-D RADIOLIB_SPI_PARANOID=0
|
||||||
-D MAX_THREADS=40
|
|
||||||
-D HAS_SCREEN=0
|
-D HAS_SCREEN=0
|
||||||
-D HAS_TFT=1
|
-D HAS_TFT=1
|
||||||
-D USE_PIN_BUZZER
|
-D USE_PIN_BUZZER
|
||||||
|
@ -169,6 +169,7 @@ static const uint8_t SCL = PIN_WIRE_SCL;
|
|||||||
#define TB_LEFT 27
|
#define TB_LEFT 27
|
||||||
#define TB_RIGHT 28
|
#define TB_RIGHT 28
|
||||||
#define TB_PRESS 29
|
#define TB_PRESS 29
|
||||||
|
#define TB_DIRECTION FALLING
|
||||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
// Compatibility Definitions
|
// Compatibility Definitions
|
||||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
@ -9,7 +9,6 @@ upload_protocol = esptool
|
|||||||
build_flags = ${esp32s3_base.build_flags}
|
build_flags = ${esp32s3_base.build_flags}
|
||||||
-DT_DECK
|
-DT_DECK
|
||||||
-DBOARD_HAS_PSRAM
|
-DBOARD_HAS_PSRAM
|
||||||
-DMAX_THREADS=40
|
|
||||||
-DGPS_POWER_TOGGLE
|
-DGPS_POWER_TOGGLE
|
||||||
-Ivariants/t-deck
|
-Ivariants/t-deck
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ build_flags =
|
|||||||
${inkhud.build_flags}
|
${inkhud.build_flags}
|
||||||
-I variants/tlora_t3s3_epaper
|
-I variants/tlora_t3s3_epaper
|
||||||
-D TLORA_T3S3_EPAPER
|
-D TLORA_T3S3_EPAPER
|
||||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
@ -58,7 +58,6 @@
|
|||||||
#define LED_STATE_ON 0 // State when LED is lit
|
#define LED_STATE_ON 0 // State when LED is lit
|
||||||
|
|
||||||
#define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode
|
#define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode
|
||||||
#define BUTTON_NEED_PULLUP2 TB_UP
|
|
||||||
#define BUTTON_PIN 0 // Circle button
|
#define BUTTON_PIN 0 // Circle button
|
||||||
#define BUTTON_NEED_PULLUP // we do need a helping hand up
|
#define BUTTON_NEED_PULLUP // we do need a helping hand up
|
||||||
#define CANCEL_BUTTON_PIN 45 // Button 1 - triangle - bottom button in landscape mode
|
#define CANCEL_BUTTON_PIN 45 // Button 1 - triangle - bottom button in landscape mode
|
||||||
|
Loading…
Reference in New Issue
Block a user