firmware/src/graphics/niche/Utils/CannedMessageStore.cpp
2025-10-16 09:36:54 -05:00

163 lines
5.5 KiB
C++

#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 = 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
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