mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-27 11:31:47 +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_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||
-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
|
||||
#-D OLED_PL=1
|
||||
|
||||
@ -103,12 +104,12 @@ lib_deps =
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||
jgromes/RadioLib@7.1.2
|
||||
jgromes/RadioLib@7.2.0
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# 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
|
||||
[environmental_base]
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6791138f0ba2b7c471072bd4bba6cbb8bacffe2d
|
||||
Subproject commit 386fa53c1596c8dfc547521f08df107f4cb3a275
|
@ -26,7 +26,7 @@
|
||||
#ifndef SLEEP_TIME
|
||||
#define SLEEP_TIME 30
|
||||
#endif
|
||||
#if EXCLUDE_POWER_FSM
|
||||
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
FakeFsm powerFSM;
|
||||
void PowerFSM_setup(){};
|
||||
#else
|
||||
|
@ -22,7 +22,7 @@
|
||||
#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
|
||||
|
||||
#if EXCLUDE_POWER_FSM
|
||||
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
class FakeFsm
|
||||
{
|
||||
public:
|
||||
|
@ -18,7 +18,7 @@ class PowerFSMThread : public OSThread
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
#if !EXCLUDE_POWER_FSM
|
||||
#if !MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
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
|
||||
|
@ -352,8 +352,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
||||
for (uint16_t i = 0; i < len; i += 16) {
|
||||
if (i % 128 == 0)
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||
char s[] = "| | | |\n";
|
||||
uint8_t ix = 1, iy = 52;
|
||||
char s[] = " | | | |\n";
|
||||
uint8_t ix = 5, iy = 56;
|
||||
for (uint8_t j = 0; j < 16; j++) {
|
||||
if (i + j < len) {
|
||||
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;
|
||||
if (i < 256)
|
||||
log(logLevel, " ");
|
||||
log(logLevel, "%02x", index);
|
||||
log(logLevel, ".");
|
||||
sprintf(s, "%03x", index);
|
||||
s[3] = '.';
|
||||
log(logLevel, s);
|
||||
}
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||
@ -393,4 +391,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
break;
|
||||
}
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "mesh/Channels.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#include "modules/WaypointModule.h"
|
||||
@ -1414,12 +1413,13 @@ int Screen::handleInputEvent(const InputEvent *event)
|
||||
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)
|
||||
case meshtastic_AdminMessage_remove_by_nodenum_tag:
|
||||
setFrames(FOCUS_PRESERVE);
|
||||
*arg->result = AdminMessageHandleResult::HANDLED;
|
||||
break;
|
||||
|
||||
// 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 "input/InputBroker.h"
|
||||
#include "mesh/MeshModule.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "power.h"
|
||||
#include <string>
|
||||
#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 InputEvent *> inputObserver =
|
||||
CallbackObserver<Screen, const InputEvent *>(this, &Screen::handleInputEvent);
|
||||
CallbackObserver<Screen, const meshtastic_AdminMessage *> adminMessageObserver =
|
||||
CallbackObserver<Screen, const meshtastic_AdminMessage *>(this, &Screen::handleAdminMessage);
|
||||
CallbackObserver<Screen, AdminModule_ObserverData *> adminMessageObserver =
|
||||
CallbackObserver<Screen, AdminModule_ObserverData *>(this, &Screen::handleAdminMessage);
|
||||
|
||||
public:
|
||||
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 handleUIFrameEvent(const UIFrameEvent *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
|
||||
void forceDisplay(bool forceUiUpdate = false);
|
||||
|
@ -19,6 +19,8 @@ namespace NicheGraphics::InkHUD
|
||||
enum MenuAction {
|
||||
NO_ACTION,
|
||||
SEND_PING,
|
||||
STORE_CANNEDMESSAGE_SELECTION,
|
||||
SEND_CANNEDMESSAGE,
|
||||
SHUTDOWN,
|
||||
NEXT_TILE,
|
||||
TOGGLE_BACKLIGHT,
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "RTC.h"
|
||||
|
||||
#include "MeshService.h"
|
||||
#include "Router.h"
|
||||
#include "airtime.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
@ -31,6 +32,12 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
|
||||
if (settings->optionalMenuItems.backlight) {
|
||||
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()
|
||||
@ -65,6 +72,10 @@ void InkHUD::MenuApplet::onForeground()
|
||||
|
||||
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:
|
||||
// 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
|
||||
@ -153,6 +164,16 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
||||
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:
|
||||
inkhud->rotate();
|
||||
break;
|
||||
@ -260,9 +281,11 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
break;
|
||||
|
||||
case SEND:
|
||||
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
|
||||
// Todo: canned messages
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
populateSendPage();
|
||||
break;
|
||||
|
||||
case CANNEDMESSAGE_RECIPIENT:
|
||||
populateRecipientPage();
|
||||
break;
|
||||
|
||||
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()
|
||||
{
|
||||
// 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.
|
||||
// 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.
|
||||
@ -619,4 +750,37 @@ uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
|
||||
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
|
@ -6,10 +6,12 @@
|
||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||
#include "graphics/niche/InkHUD/Persistence.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
#include "graphics/niche/Utils/CannedMessageStore.h"
|
||||
|
||||
#include "./MenuItem.h"
|
||||
#include "./MenuPage.h"
|
||||
|
||||
#include "Channels.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
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 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 populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
|
||||
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
|
||||
|
||||
uint16_t getSystemInfoPanelHeight();
|
||||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||
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;
|
||||
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
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace NicheGraphics::InkHUD
|
||||
enum MenuPage : uint8_t {
|
||||
ROOT, // Initial menu page
|
||||
SEND,
|
||||
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||
OPTIONS,
|
||||
APPLETS,
|
||||
AUTOSHOW,
|
||||
|
@ -13,7 +13,8 @@ using namespace NicheGraphics;
|
||||
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
|
||||
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
|
||||
// Will shortly attempt to load messages from RAM, if applet is active
|
||||
@ -69,9 +70,8 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
||||
|
||||
// Grab data for message
|
||||
MessageStore::Message &m = store->messages.at(i);
|
||||
bool outgoing = (m.sender == 0);
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
||||
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
||||
bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message
|
||||
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
||||
|
||||
// Cache bottom Y of message text
|
||||
// - Used when drawing vertical line alongside
|
||||
@ -171,54 +171,54 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
||||
void InkHUD::ThreadedMessageApplet::onActivate()
|
||||
{
|
||||
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
|
||||
// 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()
|
||||
{
|
||||
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
|
||||
// These might be incoming, from the mesh, or outgoing from phone
|
||||
// Each instance of the ThreadMessageApplet will only listen on one specific channel
|
||||
// Method should return 0, to indicate general success to TextMessageModule
|
||||
int InkHUD::ThreadedMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
||||
ProcessMessage InkHUD::ThreadedMessageApplet::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
// Abort if applet fully deactivated
|
||||
// Already handled by onActivate and onDeactivate, but good practice for all applets
|
||||
if (!isActive())
|
||||
return 0;
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
// Abort if wrong channel
|
||||
if (p->channel != this->channelIndex)
|
||||
return 0;
|
||||
if (mp.channel != this->channelIndex)
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
// Abort if message was a DM
|
||||
if (p->to != NODENUM_BROADCAST)
|
||||
return 0;
|
||||
if (mp.to != NODENUM_BROADCAST)
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
// Extract info into our slimmed-down "StoredMessage" type
|
||||
MessageStore::Message newMessage;
|
||||
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
||||
newMessage.sender = p->from;
|
||||
newMessage.channelIndex = p->channel;
|
||||
newMessage.text = std::string(&p->decoded.payload.bytes[0], &p->decoded.payload.bytes[p->decoded.payload.size]);
|
||||
newMessage.sender = mp.from;
|
||||
newMessage.channelIndex = mp.channel;
|
||||
newMessage.text = std::string((const char *)mp.decoded.payload.bytes, mp.decoded.payload.size);
|
||||
|
||||
// Store newest message at front
|
||||
// These records are used when rendering, and also stored in flash at shutdown
|
||||
store->messages.push_front(newMessage);
|
||||
|
||||
// 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();
|
||||
|
||||
// Redraw the applet, perhaps.
|
||||
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
|
||||
|
@ -30,7 +30,7 @@ namespace NicheGraphics::InkHUD
|
||||
|
||||
class Applet;
|
||||
|
||||
class ThreadedMessageApplet : public Applet
|
||||
class ThreadedMessageApplet : public Applet, public SinglePortModule
|
||||
{
|
||||
public:
|
||||
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
||||
@ -41,16 +41,11 @@ class ThreadedMessageApplet : public Applet
|
||||
void onActivate() override;
|
||||
void onDeactivate() 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
|
||||
|
||||
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 loadMessagesFromFlash();
|
||||
|
||||
|
@ -4,14 +4,13 @@
|
||||
|
||||
#include "RTC.h"
|
||||
#include "buzz.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#include "./Applet.h"
|
||||
#include "./SystemApplet.h"
|
||||
#include "graphics/niche/FlashData.h"
|
||||
#include "graphics/niche/Utils/FlashData.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
@ -30,7 +29,7 @@ void InkHUD::Events::begin()
|
||||
rebootObserver.observe(¬ifyReboot);
|
||||
textMessageObserver.observe(textMessageModule);
|
||||
#if !MESHTASTIC_EXCLUDE_ADMIN
|
||||
adminMessageObserver.observe(adminModule);
|
||||
adminMessageObserver.observe((Observable<AdminModule_ObserverData *> *)adminModule);
|
||||
#endif
|
||||
#ifdef ARCH_ESP32
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// 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_config_tag:
|
||||
eraseOnReboot = true;
|
||||
*data->result = AdminMessageHandleResult::HANDLED;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -13,7 +13,7 @@ however this class handles general events which concern InkHUD as a whole, e.g.
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "Observer.h"
|
||||
#include "modules/AdminModule.h"
|
||||
|
||||
#include "./InkHUD.h"
|
||||
#include "./Persistence.h"
|
||||
@ -33,7 +33,7 @@ class Events
|
||||
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
||||
int beforeReboot(void *unused); // Prepare for reboot
|
||||
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
|
||||
int beforeLightSleep(void *unused); // Prepare for light sleep
|
||||
#endif
|
||||
@ -54,8 +54,8 @@ class Events
|
||||
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
|
||||
|
||||
// Get notified of incoming admin messages, and handle any which are relevant to InkHUD
|
||||
CallbackObserver<Events, const meshtastic_AdminMessage *> adminMessageObserver =
|
||||
CallbackObserver<Events, const meshtastic_AdminMessage *>(this, &Events::onAdminMessage);
|
||||
CallbackObserver<Events, AdminModule_ObserverData *> adminMessageObserver =
|
||||
CallbackObserver<Events, AdminModule_ObserverData *>(this, &Events::onAdminMessage);
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// 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 "./InkHUD.h"
|
||||
#include "graphics/niche/FlashData.h"
|
||||
#include "graphics/niche/InkHUD/MessageStore.h"
|
||||
#include "graphics/niche/Utils/FlashData.h"
|
||||
|
||||
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->_pinLeft = pinLeft;
|
||||
this->_pinRight = pinRight;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventLeft = eventLeft;
|
||||
@ -20,23 +21,23 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
|
||||
|
||||
if (pinPress != 255) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, RISING);
|
||||
attachInterrupt(pinPress, onIntPress, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinDown != 255) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
||||
attachInterrupt(this->_pinDown, onIntDown, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinUp != 255) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
||||
attachInterrupt(this->_pinUp, onIntUp, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinLeft != 255) {
|
||||
pinMode(this->_pinLeft, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinLeft, onIntLeft, RISING);
|
||||
attachInterrupt(this->_pinLeft, onIntLeft, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinRight != 255) {
|
||||
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,
|
||||
@ -67,19 +68,19 @@ int32_t TrackballInterruptBase::runOnce()
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
#else
|
||||
if (this->action == TB_ACTION_PRESSED) {
|
||||
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) {
|
||||
// LOG_DEBUG("Trackball event Press");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
|
@ -3,6 +3,10 @@
|
||||
#include "InputBroker.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
#ifndef TB_DIRECTION
|
||||
#define TB_DIRECTION RISING
|
||||
#endif
|
||||
|
||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
@ -16,6 +20,7 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
||||
void intUpHandler();
|
||||
void intLeftHandler();
|
||||
void intRightHandler();
|
||||
uint32_t lastTime = 0;
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
@ -28,14 +33,15 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
||||
TB_ACTION_LEFT,
|
||||
TB_ACTION_RIGHT
|
||||
};
|
||||
|
||||
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
||||
|
||||
private:
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
uint8_t _pinLeft = 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 _eventUp = 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()
|
||||
{
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntUp()
|
||||
{
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntLeft()
|
||||
{
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntRight()
|
||||
{
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntPressed()
|
||||
{
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
|
@ -1422,7 +1422,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
|
||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AUDIO_CONFIG;
|
||||
#endif
|
||||
// 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;
|
||||
#endif
|
||||
#if NO_EXT_GPIO
|
||||
|
@ -61,12 +61,17 @@ class Default
|
||||
throttlingFactor = 0.04;
|
||||
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
|
||||
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 &&
|
||||
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO))
|
||||
return 1.0; // Don't bother throttling for highest bandwidth presets
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
|
||||
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
|
||||
int nodesOverForty = (numOnlineNodes - 40);
|
||||
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 */
|
||||
meshtastic_TelemetrySensorType_MAX17261 = 38,
|
||||
/* PCT2075 Temperature Sensor */
|
||||
meshtastic_TelemetrySensorType_PCT2075 = 39
|
||||
meshtastic_TelemetrySensorType_PCT2075 = 39,
|
||||
/* ADS1X15 ADC */
|
||||
meshtastic_TelemetrySensorType_ADS1X15 = 40
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@ -206,6 +208,36 @@ typedef struct _meshtastic_PowerMetrics {
|
||||
/* Current (Ch3) */
|
||||
bool has_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;
|
||||
|
||||
/* Air quality metrics */
|
||||
@ -360,8 +392,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_PCT2075
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_PCT2075+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_ADS1X15
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_ADS1X15+1))
|
||||
|
||||
|
||||
|
||||
@ -376,7 +408,7 @@ extern "C" {
|
||||
/* Initializer values for message structs */
|
||||
#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_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_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}
|
||||
@ -385,7 +417,7 @@ extern "C" {
|
||||
#define meshtastic_Nau7802Config_init_default {0, 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_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_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}
|
||||
@ -427,6 +459,16 @@ extern "C" {
|
||||
#define meshtastic_PowerMetrics_ch2_current_tag 4
|
||||
#define meshtastic_PowerMetrics_ch3_voltage_tag 5
|
||||
#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_pm25_standard_tag 2
|
||||
#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_current, 4) \
|
||||
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_DEFAULT NULL
|
||||
|
||||
@ -631,7 +683,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
||||
#define meshtastic_HostMetrics_size 264
|
||||
#define meshtastic_LocalStats_size 72
|
||||
#define meshtastic_Nau7802Config_size 16
|
||||
#define meshtastic_PowerMetrics_size 30
|
||||
#define meshtastic_PowerMetrics_size 81
|
||||
#define meshtastic_Telemetry_size 272
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -470,22 +470,38 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
setPassKey(&res);
|
||||
myReply = allocDataProtobuf(res);
|
||||
} 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) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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 (mp.decoded.want_response && !myReply) {
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
|
||||
}
|
||||
|
||||
// Allow any observers (e.g. the UI) to respond to this event
|
||||
notifyObservers(r);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@ -1137,7 +1153,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
|
||||
#endif
|
||||
#endif
|
||||
conn.has_serial = true; // No serial-less devices
|
||||
#if !EXCLUDE_POWER_FSM
|
||||
#if !MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
conn.serial.is_connected = powerFSM.getState() == &stateSERIAL;
|
||||
#else
|
||||
conn.serial.is_connected = powerFSM.getState();
|
||||
|
@ -6,10 +6,19 @@
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#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
|
||||
*/
|
||||
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Observable<const meshtastic_AdminMessage *>
|
||||
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Observable<AdminModule_ObserverData *>
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
|
@ -32,7 +32,6 @@ build_flags =
|
||||
${inkhud.build_flags}
|
||||
-I variants/heltec_vision_master_e213
|
||||
-D HELTEC_VISION_MASTER_E213
|
||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
||||
lib_deps =
|
||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||
${esp32s3_base.lib_deps}
|
||||
|
@ -36,7 +36,6 @@ build_flags =
|
||||
${inkhud.build_flags}
|
||||
-I variants/heltec_vision_master_e290
|
||||
-D HELTEC_VISION_MASTER_E290
|
||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
||||
lib_deps =
|
||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||
${esp32s3_base.lib_deps}
|
||||
|
@ -33,7 +33,6 @@ build_flags =
|
||||
${inkhud.build_flags}
|
||||
-I variants/heltec_wireless_paper
|
||||
-D HELTEC_WIRELESS_PAPER
|
||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
||||
lib_deps =
|
||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||
${esp32s3_base.lib_deps}
|
||||
|
@ -28,7 +28,6 @@ build_flags = ${esp32s3_base.build_flags}
|
||||
-D USE_LOG_DEBUG
|
||||
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
|
||||
-D RADIOLIB_SPI_PARANOID=0
|
||||
-D MAX_THREADS=40
|
||||
-D HAS_SCREEN=0
|
||||
-D HAS_TFT=1
|
||||
-D USE_PIN_BUZZER
|
||||
|
@ -169,6 +169,7 @@ static const uint8_t SCL = PIN_WIRE_SCL;
|
||||
#define TB_LEFT 27
|
||||
#define TB_RIGHT 28
|
||||
#define TB_PRESS 29
|
||||
#define TB_DIRECTION FALLING
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// Compatibility Definitions
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
@ -9,7 +9,6 @@ upload_protocol = esptool
|
||||
build_flags = ${esp32s3_base.build_flags}
|
||||
-DT_DECK
|
||||
-DBOARD_HAS_PSRAM
|
||||
-DMAX_THREADS=40
|
||||
-DGPS_POWER_TOGGLE
|
||||
-Ivariants/t-deck
|
||||
|
||||
|
@ -31,7 +31,6 @@ build_flags =
|
||||
${inkhud.build_flags}
|
||||
-I variants/tlora_t3s3_epaper
|
||||
-D TLORA_T3S3_EPAPER
|
||||
-D MAX_THREADS=40 ; Required if used with WiFi
|
||||
lib_deps =
|
||||
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
|
||||
${esp32s3_base.lib_deps}
|
@ -57,8 +57,7 @@
|
||||
#define LED_PIN 13 // the red part of the RGB LED
|
||||
#define LED_STATE_ON 0 // State when LED is lit
|
||||
|
||||
#define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode
|
||||
#define BUTTON_NEED_PULLUP2 TB_UP
|
||||
#define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode
|
||||
#define BUTTON_PIN 0 // Circle button
|
||||
#define BUTTON_NEED_PULLUP // we do need a helping hand up
|
||||
#define CANCEL_BUTTON_PIN 45 // Button 1 - triangle - bottom button in landscape mode
|
||||
|
Loading…
Reference in New Issue
Block a user