Compare commits

..
Author SHA1 Message Date
Tom FifieldandGitHub 8176749f74 Merge branch 'develop' into meshtasticd-notifications 2026-05-30 09:37:35 +10:00
Austin Lane 36af332622 Add libnotify to setup-native (GitHub Actions) 2026-04-20 19:13:41 -04:00
Austin Lane eb9fa6f4fe Notifications: Use libnotify
Use libnotify for meshtasticd desktop notifications.

Add "HAS_LIBNOTIFY" macro to guard for builds where libnotify is expected.
This is not included in buildroot/openwrt builds. (no libnotify except when extra repos are added).

Install desktop icon in the correct location on Debian and Fedora packages.
Update dependencies in packaging and dockerfiles.
2026-04-20 19:03:19 -04:00
Jonathan BennettandGitHub c06e152e59 Add files via upload 2026-04-20 13:46:17 -05:00
Jonathan BennettandGitHub 8b35fefdc7 Merge branch 'develop' into meshtasticd-notifications 2026-04-20 12:56:56 -05:00
Jonathan Bennett d15666611b First attempt at sending message notifications on Linux 2026-04-20 12:55:31 -05:00
39 changed files with 478 additions and 384 deletions
+1
View File
@@ -34,6 +34,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
libxcb-xkb-dev \
libxkbcommon-dev \
libinput-dev \
libnotify-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pipx install platformio
+1 -1
View File
@@ -11,4 +11,4 @@ runs:
- name: Install libs needed for native build
shell: bash
run: |
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev libnotify-dev
+2 -2
View File
@@ -16,7 +16,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
python3-pip python3-grpc-tools \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev libnotify-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
@@ -55,7 +55,7 @@ USER root
RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 libnotify4 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \
+1 -1
View File
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
g++ git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev libnotify-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir platformio==6.1.19 \
&& useradd --create-home --shell /usr/sbin/nologin meshtastic
+2 -2
View File
@@ -18,7 +18,7 @@ RUN apk --no-cache add \
py3-pip py3-grpcio-tools \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev libnotify-dev \
&& rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
@@ -49,7 +49,7 @@ USER root
RUN apk --no-cache add \
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
i2c-tools libuv libx11 libinput libxkbcommon sdl2 libnotify \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \
+2 -1
View File
@@ -27,7 +27,8 @@ Build-Depends: debhelper-compat (= 13),
libinput-dev,
libxkbcommon-x11-dev,
libsqlite3-dev,
libsdl2-dev
libsdl2-dev,
libnotify-dev
Standards-Version: 4.6.2
Homepage: https://github.com/meshtastic/firmware
Rules-Requires-Root: no
+2
View File
@@ -7,3 +7,5 @@ bin/meshtasticd.service lib/systemd/system
bin/meshtasticd-start.sh usr/bin
web/* usr/share/meshtasticd/web
bin/org.meshtastic.meshtasticd.svg usr/share/icons/hicolor/scalable/apps
+6
View File
@@ -50,6 +50,7 @@ BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11)
BuildRequires: pkgconfig(sdl2)
BuildRequires: pkgconfig(libnotify)
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
@@ -112,6 +113,10 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
# Install default SSL storage directory (for web)
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
# Install desktop icon
mkdir -p %{buildroot}%{_datadir}/icons/hicolor/scalable/apps
install -m 0644 bin/org.meshtastic.meshtasticd.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/org.meshtastic.meshtasticd.svg
%pre
# create spi group (for udev rules)
getent group spi > /dev/null || groupadd -r spi
@@ -171,6 +176,7 @@ fi
%dir %{_datadir}/meshtasticd/web
%{_datadir}/meshtasticd/web/*
%dir %{_sysconfdir}/meshtasticd/ssl
%{_datadir}/icons/hicolor/scalable/apps/org.meshtastic.meshtasticd.svg
%changelog
%autochangelog
+1 -1
View File
@@ -1,5 +1,5 @@
#include "configuration.h"
#if HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
#if HAS_SCREEN
#include "FSCommon.h"
#include "MessageStore.h"
#include "NodeDB.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
#if HAS_SCREEN
// Disable debug logging entirely on release builds of HELTEC_MESH_SOLAR for space constraints
#if defined(HELTEC_MESH_SOLAR)
-5
View File
@@ -31,11 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#if __has_include("SensorRtcHelper.hpp")
#include "SensorRtcHelper.hpp"
// SensorLib defines isBitSet as a macro; undefine it here to avoid conflicts
// with the SparkFun MMC5983MA library, which has a class method of the same name.
#ifdef isBitSet
#undef isBitSet
#endif
#endif
/* Offer chance for variant-specific defines */
@@ -3,7 +3,6 @@
#include "./NotificationApplet.h"
#include "./Notification.h"
#include "MessageStore.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "meshUtils.h"
@@ -232,7 +231,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
bool msgIsBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
// Pick source of message
const StoredMessage *message =
const MessageStore::Message *message =
msgIsBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
// Find info about the sender
@@ -262,7 +261,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
text += hexifyNodeNum(message->sender);
text += ": ";
text += MessageStore::getText(*message);
text += message->text;
}
}
@@ -2,8 +2,6 @@
#include "./AllMessageApplet.h"
#include "MessageStore.h"
using namespace NicheGraphics;
void InkHUD::AllMessageApplet::onActivate()
@@ -39,7 +37,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
void InkHUD::AllMessageApplet::onRender(bool full)
{
// Find newest message, regardless of whether DM or broadcast
StoredMessage *message;
MessageStore::Message *message;
if (latestMessage->wasBroadcast)
message = &latestMessage->broadcast;
else
@@ -98,7 +96,7 @@ void InkHUD::AllMessageApplet::onRender(bool full)
// ===================
// Parse any non-ascii chars in the message
std::string text = parse(std::string(MessageStore::getText(*message)));
std::string text = parse(message->text);
// Extra gap below the header
int16_t textTop = headerDivY + padDivH;
@@ -2,8 +2,6 @@
#include "./DMApplet.h"
#include "MessageStore.h"
using namespace NicheGraphics;
void InkHUD::DMApplet::onActivate()
@@ -94,7 +92,7 @@ void InkHUD::DMApplet::onRender(bool full)
// ===================
// Parse any non-ascii chars in the message
std::string text = parse(std::string(MessageStore::getText(latestMessage->dm)));
std::string text = parse(latestMessage->dm.text);
// Extra gap below the header
int16_t textTop = headerDivY + padDivH;
@@ -7,9 +7,19 @@
using namespace NicheGraphics;
// Hard limits on how much message data to write to flash
// Avoid filling the storage if something goes wrong
// Normal usage should be well below this size
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
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
// Label (filename in flash) is set from channel index
store = new MessageStore("ch" + to_string(channelIndex));
}
void InkHUD::ThreadedMessageApplet::onRender(bool full)
@@ -51,24 +61,17 @@ void InkHUD::ThreadedMessageApplet::onRender(bool full)
const uint16_t msgW = (msgR - msgL) + 1;
int16_t msgB = height() - 1; // Vertical cursor for drawing. Messages are bottom-aligned to this value.
uint8_t i = 0; // Index of stored message
// Iterate the global store newest-first, showing only broadcast messages on our channel
const auto &allMessages = messageStore.getLiveMessages();
int msgIdx = (int)allMessages.size() - 1;
while (msgB >= (0 - fontSmall.lineHeight()) && msgIdx >= 0) {
const StoredMessage &m = allMessages.at(msgIdx);
// Skip messages that don't belong to this channel or are DMs
if (m.type != MessageType::BROADCAST || m.channelIndex != channelIndex) {
msgIdx--;
continue;
}
// Loop over messages
// - until no messages left, or
// - until no part of message fits on screen
while (msgB >= (0 - fontSmall.lineHeight()) && i < store->messages.size()) {
// Grab data for message
bool outgoing = (m.sender == myNodeInfo.my_node_num);
std::string bodyText = parse(std::string(MessageStore::getText(m))); // Parse any non-ascii chars
const MessageStore::Message &m = store->messages.at(i);
bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
// Cache bottom Y of message text
// - Used when drawing vertical line alongside
@@ -149,13 +152,18 @@ void InkHUD::ThreadedMessageApplet::onRender(bool full)
// Move cursor up: padding before next message
msgB -= fontSmall.lineHeight() * 0.5;
msgIdx--;
i++;
} // End of loop: drawing each message
// Fade effect:
// Area immediately below the divider. Overdraw with sparse white lines.
// Make text appear to pass behind the header
hatchRegion(0, dividerY + 1, width(), fontSmall.lineHeight() / 3, 2, WHITE);
// If we've run out of screen to draw messages, we can drop any leftover data from the queue
// Those messages have been pushed off the screen-top by newer ones
while (i < store->messages.size())
store->messages.pop_back();
}
// Code which runs when the applet begins running
@@ -190,8 +198,16 @@ ProcessMessage InkHUD::ThreadedMessageApplet::handleReceived(const meshtastic_Me
if (mp.to != NODENUM_BROADCAST)
return ProcessMessage::CONTINUE;
// Store in the global messageStore — this handles sender, timestamp, channel, text, and ack status
messageStore.addFromPacket(mp);
// Extract info into our slimmed-down "StoredMessage" type
MessageStore::Message newMessage;
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
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(&mp) != nodeDB->getNodeNum())
@@ -216,25 +232,37 @@ bool InkHUD::ThreadedMessageApplet::approveNotification(Notification &n)
return true;
}
// Save messages to flash via the global messageStore.
// The global store holds messages for all channels; no per-channel file is needed.
// Save several recent messages to flash
// Stores the contents of ThreadedMessageApplet::messages
// Just enough messages to fill the display
// Messages are packed "back-to-back", to minimize blocks of flash used
void InkHUD::ThreadedMessageApplet::saveMessagesToFlash()
{
messageStore.saveToFlash();
// Create a label (will become the filename in flash)
std::string label = "ch" + to_string(channelIndex);
store->saveToFlash();
}
// Messages are loaded once by InkHUD::begin() before applets start.
// Nothing to do here at per-applet activation time.
// Load recent messages to flash
// Fills ThreadedMessageApplet::messages with previous messages
// Just enough messages have been stored to cover the display
void InkHUD::ThreadedMessageApplet::loadMessagesFromFlash()
{
// No-op: messageStore.loadFromFlash() is called in InkHUD::begin()
// Create a label (will become the filename in flash)
std::string label = "ch" + to_string(channelIndex);
store->loadFromFlash();
}
// Code to run when device is shutting down
// This is in addition to any onDeactivate() code, which will also run
// Todo: implement before a reboot also
void InkHUD::ThreadedMessageApplet::onShutdown()
{
// messageStore.saveToFlash() is called centrally by Events::beforeDeepSleep / beforeReboot
// Save our current set of messages to flash, provided the applet isn't disabled
if (isActive())
saveMessagesToFlash();
}
#endif
#endif
@@ -20,8 +20,8 @@ Suggest a max of two channel, to minimize fs usage?
#include "configuration.h"
#include "MessageStore.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/MessageStore.h"
#include "modules/TextMessageModule.h"
@@ -49,6 +49,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
void saveMessagesToFlash();
void loadMessagesFromFlash();
MessageStore *store; // Messages, held in RAM for use, ready to save to flash on shutdown
uint8_t channelIndex = 0;
};
+25 -22
View File
@@ -2,7 +2,6 @@
#include "./Events.h"
#include "MessageStore.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "buzz.h"
@@ -515,7 +514,6 @@ int InkHUD::Events::beforeReboot(void *unused)
inkhud->persistence->saveLatestMessage();
} else {
NicheGraphics::clearFlashData();
messageStore.clearAllMessages(); // also wipe the shared message store
}
// Note: no forceUpdate call here
@@ -534,27 +532,32 @@ int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
if (getFrom(packet) == nodeDB->getNodeNum())
return 0;
bool isBroadcastMsg = isBroadcast(packet->to);
inkhud->persistence->latestMessage.wasBroadcast = isBroadcastMsg;
// Determine whether the message is broadcast or a DM
// Store this info to prevent confusion after a reboot
// Avoids need to compare timestamps, because of situation where "future" messages block newly received, if time not set
inkhud->persistence->latestMessage.wasBroadcast = isBroadcast(packet->to);
if (!isBroadcastMsg) {
// DMs never pass through ThreadedMessageApplet, so add them to the global store here
// so they survive reboots. Derive the latestMessage cache entry from the stored result.
inkhud->persistence->latestMessage.dm = messageStore.addFromPacket(*packet);
} else {
// Broadcasts are added to the global store by ThreadedMessageApplet::handleReceived().
// Here we only update the latestMessage cache used by AllMessageApplet / NotificationApplet.
StoredMessage &sm = inkhud->persistence->latestMessage.broadcast;
sm.sender = packet->from;
sm.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true);
sm.channelIndex = packet->channel;
const char *payload = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
size_t storedLen = packet->decoded.payload.size;
if (storedLen >= MAX_MESSAGE_SIZE)
storedLen = MAX_MESSAGE_SIZE - 1;
sm.textOffset = MessageStore::storeText(payload, storedLen);
sm.textLength = static_cast<uint16_t>(storedLen);
}
// Pick the appropriate variable to store the message in
MessageStore::Message *storedMessage = inkhud->persistence->latestMessage.wasBroadcast
? &inkhud->persistence->latestMessage.broadcast
: &inkhud->persistence->latestMessage.dm;
// Store nodenum of the sender
// Applets can use this to fetch user data from nodedb, if they want
storedMessage->sender = packet->from;
// Store the time (epoch seconds) when message received
storedMessage->timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
// Store the channel
// - (potentially) used to determine whether notification shows
// - (potentially) used to determine which applet to focus
storedMessage->channelIndex = packet->channel;
// Store the text
// Need to specify manually how many bytes, because source not null-terminated
storedMessage->text =
std::string(&packet->decoded.payload.bytes[0], &packet->decoded.payload.bytes[packet->decoded.payload.size]);
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
}
-95
View File
@@ -9,10 +9,6 @@
#include "./SystemApplet.h"
#include "./Tile.h"
#include "./WindowManager.h"
#include "FSCommon.h"
#include "MessageStore.h"
#include "SPILock.h"
#include "concurrency/LockGuard.h"
using namespace NicheGraphics;
@@ -64,102 +60,11 @@ void InkHUD::InkHUD::notifyApplyingChanges()
}
}
// One-time migration from the old per-channel InkHUD message files (/NicheGraphics/ch*.msgs)
// to the firmware-wide MessageStore format (/Messages_default.msgs).
// Only runs when the new store loaded empty, meaning this is the first boot after the format change.
// Old files are deleted once migrated.
static void migrateOldInkHUDMessages()
{
#ifdef FSCom
bool migrated = false;
constexpr uint8_t MAX_CHANNELS = 8;
constexpr uint32_t OLD_MAX_MSG_SIZE = 250;
for (uint8_t ch = 0; ch < MAX_CHANNELS; ch++) {
std::string path = "/NicheGraphics/ch";
path += std::to_string(ch);
path += ".msgs";
spiLock->lock();
bool exists = FSCom.exists(path.c_str());
spiLock->unlock();
if (!exists)
continue;
concurrency::LockGuard guard(spiLock);
auto f = FSCom.open(path.c_str(), FILE_O_READ);
if (!f || f.size() == 0) {
if (f)
f.close();
FSCom.remove(path.c_str());
continue;
}
uint8_t count = 0;
f.readBytes(reinterpret_cast<char *>(&count), 1);
std::vector<StoredMessage> channelMsgs;
for (uint8_t i = 0; i < count; i++) {
StoredMessage sm;
f.readBytes(reinterpret_cast<char *>(&sm.timestamp), sizeof(sm.timestamp));
f.readBytes(reinterpret_cast<char *>(&sm.sender), sizeof(sm.sender));
f.readBytes(reinterpret_cast<char *>(&sm.channelIndex), sizeof(sm.channelIndex));
char textBuf[OLD_MAX_MSG_SIZE + 1] = {};
uint32_t textLen = 0;
char c;
while (textLen < OLD_MAX_MSG_SIZE) {
if (f.readBytes(&c, 1) != 1)
break;
if (c == '\0')
break;
textBuf[textLen++] = c;
}
sm.dest = NODENUM_BROADCAST;
sm.type = MessageType::BROADCAST;
sm.isBootRelative = false;
sm.ackStatus = AckStatus::ACKED;
size_t storedLen = (textLen >= MAX_MESSAGE_SIZE) ? MAX_MESSAGE_SIZE - 1 : textLen;
sm.textOffset = MessageStore::storeText(textBuf, storedLen);
sm.textLength = static_cast<uint16_t>(storedLen);
channelMsgs.push_back(sm);
}
// Old format stored newest-first (push_front); insert oldest-first for correct chronological order
for (int i = static_cast<int>(channelMsgs.size()) - 1; i >= 0; i--)
messageStore.addLiveMessage(channelMsgs[i]);
if (!channelMsgs.empty())
migrated = true;
f.close();
FSCom.remove(path.c_str());
LOG_INFO("Migrated %u messages from %s", static_cast<uint32_t>(count), path.c_str());
}
// Delete the old latest.msgs; the latestMessage cache will be re-derived from migrated channel messages
spiLock->lock();
if (FSCom.exists("/NicheGraphics/latest.msgs"))
FSCom.remove("/NicheGraphics/latest.msgs");
spiLock->unlock();
if (migrated) {
LOG_INFO("InkHUD message migration complete, saving to new format");
messageStore.saveToFlash();
}
#endif
}
// Start InkHUD!
// Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin()
{
persistence->loadSettings();
messageStore.loadFromFlash(); // Load persisted messages before deriving latestMessage cache
if (messageStore.getLiveMessages().empty())
migrateOldInkHUDMessages(); // First boot after format change: import old per-channel files
persistence->loadLatestMessage();
windowManager->begin();
+156
View File
@@ -0,0 +1,156 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./MessageStore.h"
#include "SafeFile.h"
using namespace NicheGraphics;
// Hard limits on how much message data to write to flash
// Avoid filling the storage if something goes wrong
// Normal usage should be well below this size
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
InkHUD::MessageStore::MessageStore(const std::string &label)
{
filename = "";
filename += "/NicheGraphics";
filename += "/";
filename += label;
filename += ".msgs";
}
// Write the contents of the MessageStore::messages object to flash
// Takes the firmware's SPI lock during FS operations. Implemented for consistency, but only relevant when using SD card.
// Need to lock and unlock around specific FS methods, as the SafeFile class takes the lock for itself internally
void InkHUD::MessageStore::saveToFlash()
{
assert(!filename.empty());
#ifdef FSCom
// Make the directory, if doesn't already exist
// This is the same directory accessed by NicheGraphics::FlashData
spiLock->lock();
FSCom.mkdir("/NicheGraphics");
spiLock->unlock();
// Open or create the file
// No "full atomic": don't save then rename
auto f = SafeFile(filename.c_str(), false);
LOG_INFO("Saving messages in %s", filename.c_str());
// Take firmware's SPI Lock while writing
spiLock->lock();
// 1st byte: how many messages will be written to store
f.write(messages.size());
// For each message
for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) {
Message &m = messages.at(i);
f.write(reinterpret_cast<const uint8_t *>(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes
f.write(reinterpret_cast<const uint8_t *>(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes
f.write(reinterpret_cast<const uint8_t *>(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()),
min((size_t)MAX_MESSAGE_SIZE, m.text.size())); // Write message text
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i),
min((size_t)MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str());
}
// Release firmware's SPI lock, because SafeFile::close needs it
spiLock->unlock();
bool writeSucceeded = f.close();
if (!writeSucceeded) {
LOG_ERROR("Can't write data!");
}
#else
LOG_ERROR("ERROR: Filesystem not implemented\n");
#endif
}
// Attempt to load the previous contents of the MessageStore:message deque from flash.
// Filename is controlled by the "label" parameter
// Takes the firmware's SPI lock during FS operations. Implemented for consistency, but only relevant when using SD card.
void InkHUD::MessageStore::loadFromFlash()
{
// Hopefully redundant. Initial intention is to only load / save once per boot.
messages.clear();
#ifdef FSCom
// Take the firmware's SPI Lock, in case filesystem is on SD card
concurrency::LockGuard guard(spiLock);
// Check that the file *does* actually exist
if (!FSCom.exists(filename.c_str())) {
LOG_WARN("'%s' not found. Using default values", filename.c_str());
return;
}
// Check that the file *does* actually exist
if (!FSCom.exists(filename.c_str())) {
LOG_INFO("'%s' not found.", filename.c_str());
return;
}
// Open the file
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
if (f.size() == 0) {
LOG_INFO("%s is empty", filename.c_str());
f.close();
return;
}
// If opened, start reading
if (f) {
LOG_INFO("Loading threaded messages '%s'", filename.c_str());
// First byte: how many messages are in the flash store
uint8_t flashMessageCount = 0;
f.readBytes(reinterpret_cast<char *>(&flashMessageCount), 1);
LOG_DEBUG("Messages available: %u", static_cast<uint32_t>(flashMessageCount));
// For each message
for (uint8_t i = 0; i < flashMessageCount && i < MAX_MESSAGES_SAVED; i++) {
Message m;
// Read meta data (fixed width)
f.readBytes(reinterpret_cast<char *>(&m.timestamp), sizeof(m.timestamp));
f.readBytes(reinterpret_cast<char *>(&m.sender), sizeof(m.sender));
f.readBytes(reinterpret_cast<char *>(&m.channelIndex), sizeof(m.channelIndex));
// Read characters until we find a null term
char c;
while (m.text.size() < MAX_MESSAGE_SIZE) {
f.readBytes(&c, 1);
if (c != '\0')
m.text += c;
else
break;
}
// Store in RAM
messages.push_back(m);
LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", static_cast<uint32_t>(i), m.timestamp, m.sender,
m.text.c_str());
}
f.close();
} else {
LOG_ERROR("Could not open / read %s", filename.c_str());
}
#else
LOG_ERROR("Filesystem not implemented");
state = LoadFileState::NO_FILESYSTEM;
#endif
return;
}
#endif
+47
View File
@@ -0,0 +1,47 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
We hold a few recent messages, for features like the threaded message applet.
This class contains a struct for storing those messages,
and methods for serializing them to flash.
*/
#pragma once
#include "configuration.h"
#include <deque>
#include "mesh/MeshTypes.h"
namespace NicheGraphics::InkHUD
{
class MessageStore
{
public:
// A stored message
struct Message {
uint32_t timestamp; // Epoch seconds
NodeNum sender = 0;
uint8_t channelIndex;
std::string text;
};
MessageStore() = delete;
explicit MessageStore(const std::string &label); // Label determines filename in flash
void saveToFlash();
void loadFromFlash();
std::deque<Message> messages; // Interact with this object!
private:
std::string filename;
};
} // namespace NicheGraphics::InkHUD
#endif
+21 -16
View File
@@ -17,23 +17,23 @@ void InkHUD::Persistence::loadSettings()
LOG_WARN("Settings version changed. Using defaults");
}
// Rebuild the latestMessage cache from the global messageStore.
// Called after messageStore.loadFromFlash() so that the most recent broadcast and DM
// are immediately available to applets (DMApplet, AllMessageApplet, NotificationApplet).
// Load settings and latestMessage data
void InkHUD::Persistence::loadLatestMessage()
{
int lastBroadcastPos = -1, lastDMPos = -1, pos = 0;
for (const StoredMessage &m : messageStore.getLiveMessages()) {
if (m.type == MessageType::BROADCAST) {
latestMessage.broadcast = m;
lastBroadcastPos = pos;
} else if (m.type == MessageType::DM_TO_US) {
latestMessage.dm = m;
lastDMPos = pos;
}
pos++;
// Load previous "latestMessages" data from flash
MessageStore store("latest");
store.loadFromFlash();
// Place into latestMessage struct, for convenient access
// Number of strings loaded determines whether last message was broadcast or dm
if (store.messages.size() == 1) {
latestMessage.dm = store.messages.at(0);
latestMessage.wasBroadcast = false;
} else if (store.messages.size() == 2) {
latestMessage.dm = store.messages.at(0);
latestMessage.broadcast = store.messages.at(1);
latestMessage.wasBroadcast = true;
}
latestMessage.wasBroadcast = (lastBroadcastPos > lastDMPos);
}
// Save the InkHUD settings to flash
@@ -42,10 +42,15 @@ void InkHUD::Persistence::saveSettings()
FlashData<Settings>::save(&settings, "settings");
}
// Persist all messages via the global messageStore.
// Save latestMessage data to flash
void InkHUD::Persistence::saveLatestMessage()
{
messageStore.saveToFlash();
// Number of strings saved determines whether last message was broadcast or dm
MessageStore store("latest");
store.messages.push_back(latestMessage.dm);
if (latestMessage.wasBroadcast)
store.messages.push_back(latestMessage.broadcast);
store.saveToFlash();
}
/*
+6 -6
View File
@@ -15,7 +15,7 @@ The save / load mechanism is a shared NicheGraphics feature.
#include "configuration.h"
#include "./InkHUD.h"
#include "MessageStore.h"
#include "graphics/niche/InkHUD/MessageStore.h"
#include "graphics/niche/Utils/FlashData.h"
namespace NicheGraphics::InkHUD
@@ -120,12 +120,12 @@ class Persistence
};
// Most recently received text message
// Value is updated by InkHUD::Events, as a courtesy to applets.
// Populated at boot from the global messageStore, then updated live on receive.
// Value is updated by InkHUD::WindowManager, as a courtesy to applets
// InkHUD keeps its own latest-message cache for applets.
struct LatestMessage {
StoredMessage broadcast; // Most recent broadcast message received
StoredMessage dm; // Most recent DM received
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
MessageStore::Message broadcast; // Most recent message received broadcast
MessageStore::Message dm; // Most recent received DM
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
};
void loadSettings();
+11 -20
View File
@@ -422,11 +422,9 @@ Stores InkHUD data in flash
- settings
- most recent text message received (both for broadcast and DM)
Message history (used by `ThreadedMessageApplet`) is stored by the firmware-wide `MessageStore` (`src/MessageStore.h`), not by `Persistence` directly.
In rare cases, applets may store their own specific data separately (e.g. `ThreadedMessageApplet`)
Settings are saved only on shutdown / reboot. Not saved if power is removed unexpectedly.
Message history is saved periodically (every 2 hours by default), as well as on shutdown / reboot.
Data saved only on shutdown / reboot. Not saved if power is removed unexpectedly.
---
@@ -468,21 +466,18 @@ Collected here, so various user applets don't all have to store their own copy o
We keep this separate latest-message cache for this purpose, because:
- we want to expose both the most recent broadcast and most recent DM independently
- applets like `DMApplet` and `NotificationApplet` need quick access without scanning the full message history
#### How messages reach the store
Broadcasts and DMs take different paths into `messageStore`:
- **Broadcasts** — `ThreadedMessageApplet::handleReceived()` calls `messageStore.addFromPacket()`. `Events::onReceiveTextMessage()` then updates `latestMessage.broadcast` separately for fast access by `AllMessageApplet` and `NotificationApplet`.
- **DMs** — `ThreadedMessageApplet` skips DMs entirely. `Events::onReceiveTextMessage()` calls `messageStore.addFromPacket()` directly and stores the result in `latestMessage.dm`.
- it is cleared by an outgoing text message
- we want to store both a recent broadcast and a recent DM
#### Saving / Loading
The `LatestMessage` cache is not persisted to its own file. On boot, `InkHUD::begin()` calls `messageStore.loadFromFlash()` first, then `Persistence::loadLatestMessage()` rebuilds the cache by scanning the loaded messages for the most recent broadcast and DM.
_A bit of a hack.._
Stored to flash using `InkHUD::MessageStore`, which is really intended for storing a thread of messages (see `ThreadedMessageApplet`). Used because it stores strings more efficiently than `FlashData.h`.
Text is stored in the firmware-wide shared text pool. Use `MessageStore::getText(msg)` to retrieve it from a `StoredMessage`.
The hack is:
- If most recent message was a DM, we only store the DM.
- If most recent message was a broadcast, we store both a DM and a broadcast. The DM may be 0-length string.
---
@@ -587,17 +582,13 @@ Handles events which impact the InkHUD system generally (e.g. shutdown, button p
Applets themselves do also listen separately for various events, but for the purpose of gathering information which they would like to display.
#### Text Messages
`Events::onReceiveTextMessage()` is the central handler for all incoming text messages. It updates the `LatestMessage` cache and, for DMs, also adds the message to `messageStore` (since `ThreadedMessageApplet` only handles broadcasts). See `Persistence::LatestMessage` for details on how the two message types are stored.
#### Buttons
Button input is sometimes handled by a system applet. `InkHUD::Events` determines whether the button should be handled by a specific system applet, or should instead trigger a default behavior
#### Factory Reset
The Events class handles the admin message(s) which trigger factory reset. We set `Events::eraseOnReboot = true`, which causes `Events::onReboot` to erase the contents of InkHUD's data directory (`/NicheGraphics/`) and also call `messageStore.clearAllMessages()` to wipe the firmware-wide message store (`/Messages_default.msgs`). Both are cleared during reboot rather than earlier, to avoid data being re-written by applets still running before shutdown.
The Events class handles the admin messages(s) which trigger factory reset. We set `Events::eraseOnReboot = true`, which causes `Events::onReboot` to erase the contents of InkHUD's data directory. We do this because some applets (e.g. ThreadedMessageApplet) save their own data to flash, so if we erased earlier, that data would get re-written during reboot.
---
+1 -1
View File
@@ -1243,7 +1243,7 @@ void loop()
}
#endif
#endif
#if (HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)) && ENABLE_MESSAGE_PERSISTENCE
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
messageStoreAutosaveTick();
#endif
long delayMsec = mainController.runOrDelay();
+4 -4
View File
@@ -15,7 +15,7 @@ static const meshtastic_Config_LoRaConfig_ModemPreset MODEM_PRESET_END =
#define PRESET(name) meshtastic_Config_LoRaConfig_ModemPreset_##name
// Override slot magic numbers for RegionInfo.overrideSlot
// Override slot magic numbers for RegionProfile.overrideSlot
#define OVERRIDE_SLOT_DEFAULT_CHANNEL_HASH 0 // Use hash of primary channel name
#define OVERRIDE_SLOT_PRESET_HASH -1 // Use hash of preset name instead
// Positive values (1-32767) are explicit slot numbers
@@ -30,6 +30,8 @@ struct RegionProfile {
int8_t textThrottle; // throttle for text - future expansion
int8_t positionThrottle; // throttle for location data - future expansion
int8_t telemetryThrottle; // throttle for telemetry - future expansion
int16_t overrideSlot; // a per-region override slot for if we need to fix it in place
// Magic values: 0 = use channel name hash, -1 = use preset name hash, >0 = explicit slot
};
/**
@@ -57,9 +59,7 @@ struct RegionInfo {
bool wideLora;
const RegionProfile *profile;
meshtastic_Config_LoRaConfig_ModemPreset defaultPreset;
int16_t overrideSlot; // per-region override slot for frequency selection
// Magic values: 0 = use channel name hash, -1 = use preset name hash, >0 = explicit slot
const char *name; // EU433 etc
const char *name; // EU433 etc
// Preset accessors (delegate through profile)
meshtastic_Config_LoRaConfig_ModemPreset getDefaultPreset() const { return defaultPreset; }
+4
View File
@@ -1109,6 +1109,10 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
#endif
#if ARCH_PORTDUINO
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.alert_message = true;
#endif
#ifdef NANO_G2_ULTRA
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.alert_message = true;
+52 -52
View File
@@ -50,18 +50,17 @@ static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_NARROW[] = {PRESET
MODEM_PRESET_END};
// Region profiles: bundle preset list + regulatory parameters shared across regions
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 1, 1};
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 1, 1};
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 1, 1};
const RegionProfile PROFILE_LITE = {PRESETS_LITE, 0.4, 0.0375f, false, false, 0, 10, 10};
const RegionProfile PROFILE_NARROW = {PRESETS_NARROW, 0, 0.0104f, true, false, 0, 1, 1};
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle, override slot
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 1, 1, 0};
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 1, 1, 0};
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 1, 1, 0};
const RegionProfile PROFILE_LITE = {PRESETS_LITE, 0.4, 0.0375f, false, false, 0, 10, 10, 0};
const RegionProfile PROFILE_NARROW = {PRESETS_NARROW, 0, 0.0104f, true, false, 0, 1, 1, 1};
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr, default_preset, \
override_slot) \
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr, default_preset) \
{ \
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, \
wide_lora, &profile_ptr, default_preset, override_slot, #name \
wide_lora, &profile_ptr, default_preset, #name \
}
const RegionInfo regions[] = {
@@ -69,7 +68,7 @@ const RegionInfo regions[] = {
https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
*/
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
@@ -77,7 +76,7 @@ const RegionInfo regions[] = {
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
FIXME: https://github.com/meshtastic/firmware/issues/3371
*/
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
@@ -92,33 +91,33 @@ const RegionInfo regions[] = {
AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
*/
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868, PRESET(LONG_FAST), 0),
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
*/
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
Also used in Brazil.
*/
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100
*/
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf
@@ -126,13 +125,13 @@ const RegionInfo regions[] = {
Note:
- We do LBT, so 100% is allowed.
*/
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0
https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters
*/
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
@@ -140,40 +139,40 @@ const RegionInfo regions[] = {
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
*/
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
*/
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
Thailand 920925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
*/
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
433,05-434,7 Mhz 10 mW
868,0-868,6 Mhz 25 mW
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
*/
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Malaysia
433 - 435 MHz at 100mW, no restrictions.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Malaysia
@@ -182,14 +181,14 @@ const RegionInfo regions[] = {
Frequency hopping is used for 919 - 923 MHz.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Singapore
SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf
*/
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Philippines
@@ -199,9 +198,9 @@ const RegionInfo regions[] = {
https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
*/
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Kazakhstan
@@ -209,46 +208,46 @@ const RegionInfo regions[] = {
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
https://github.com/meshtastic/firmware/issues/7204
*/
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Nepal
865MHz to 868MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
*/
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Brazil
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions
https://github.com/meshtastic/firmware/issues/3741
*/
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
*/
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD, PRESET(LONG_FAST), 0),
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD, PRESET(LONG_FAST)),
/*
EU 866MHz band (Band no. 46b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD)
Gives 4 channels at 865.7/866.3/866.9/867.5 MHz, 400 kHz gap plus 37.5 kHz padding between channels, 27 dBm,
duty cycle 2.5% (mobile) or 10% (fixed) https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02006D0771(01)-20250123
*/
RDEF(EU_866, 865.6f, 867.6f, 2.5, 27, false, false, PROFILE_LITE, PRESET(LITE_FAST), 0),
RDEF(EU_866, 865.6f, 867.6f, 2.5, 27, false, false, PROFILE_LITE, PRESET(LITE_FAST)),
/*
EU 868MHz band: 3 channels at 869.410/869.4625/869.577 MHz
Channel centres at 869.442/869.525/869.608 MHz,
10.4 kHz padding on channels, 27 dBm, duty cycle 10%
*/
RDEF(EU_N_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_NARROW, PRESET(NARROW_SLOW), 1),
RDEF(EU_N_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_NARROW, PRESET(NARROW_SLOW)),
/*
This needs to be last. Same as US.
*/
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF, PRESET(LONG_FAST), 0),
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF, PRESET(LONG_FAST)),
};
@@ -933,11 +932,11 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo
// overrideSlot: 0 = channel hash, -1 = preset hash, >0 = explicit slot
uses_default_frequency_slot =
(loraConfig.channel_num == 0) || // user choice unset, no frequency override, so use default
(newRegion->overrideSlot > 0 &&
loraConfig.channel_num == newRegion->overrideSlot) || // user setting matches explicit override slot
((newRegion->overrideSlot == OVERRIDE_SLOT_DEFAULT_CHANNEL_HASH) &&
(newRegion->profile->overrideSlot > 0 &&
loraConfig.channel_num == newRegion->profile->overrideSlot) || // user setting matches explicit override slot
((newRegion->profile->overrideSlot == OVERRIDE_SLOT_DEFAULT_CHANNEL_HASH) &&
((uint32_t)(loraConfig.channel_num - 1) == channelNameHashSlot)) || // user setting matches channel name hash
((newRegion->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) &&
((newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) &&
((uint32_t)(loraConfig.channel_num - 1) == presetNameHashSlot)); // user setting matches preset name hash
// check if user setting different to preset name
@@ -953,11 +952,12 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo
if (clamp) {
if (uses_custom_channel_name) { // clamp to channel name hash
loraConfig.channel_num =
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
} else if (newRegion->overrideSlot > 0) { // clamp to explicit override slot
loraConfig.channel_num = newRegion->overrideSlot; // use the explicit override slot defined for this region
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
} else if (newRegion->profile->overrideSlot > 0) { // clamp to explicit override slot
loraConfig.channel_num =
newRegion->profile->overrideSlot; // use the explicit override slot specified by the region profile
uses_default_frequency_slot = true;
} else if (newRegion->overrideSlot == OVERRIDE_SLOT_PRESET_HASH && loraConfig.use_preset) {
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH && loraConfig.use_preset) {
// clamp to preset name hash
loraConfig.channel_num = presetNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
uses_default_frequency_slot = true;
@@ -1076,9 +1076,9 @@ void RadioInterface::applyModemConfig()
// NB: channel_num is also know as frequency slot but it's too late to fix now.
if (uses_default_frequency_slot) {
// Handle three override slot cases: explicit slot (>0), preset hash (-1), or channel hash (0)
if (newRegion->overrideSlot > 0) {
channel_num = newRegion->overrideSlot - 1; // explicit override slot (1-based to 0-based)
} else if (newRegion->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
if (newRegion->profile->overrideSlot > 0) {
channel_num = newRegion->profile->overrideSlot - 1; // explicit override slot (1-based to 0-based)
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
channel_num = presetNameHashSlot; // use preset name hash
} else {
channel_num = channelNameHashSlot; // use channel name hash (default case)
@@ -1111,9 +1111,9 @@ void RadioInterface::applyModemConfig()
LOG_INFO("newRegion->freqStart -> newRegion->freqEnd: %f -> %f (%f MHz)", newRegion->freqStart, newRegion->freqEnd,
newRegion->freqEnd - newRegion->freqStart);
LOG_INFO("numFreqSlots: %u x %.3fkHz", numFreqSlots, bw);
if (newRegion->overrideSlot > 0) {
LOG_INFO("Using region explicit override slot: %d", newRegion->overrideSlot);
} else if (newRegion->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
if (newRegion->profile->overrideSlot > 0) {
LOG_INFO("Using region explicit override slot: %d", newRegion->profile->overrideSlot);
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
LOG_INFO("Using region preset name hash for slot selection");
}
LOG_INFO("channel_num: %d", channel_num + 1);
@@ -24,6 +24,10 @@
#include "mesh/generated/meshtastic/rtttl.pb.h"
#include <Arduino.h>
#if HAS_LIBNOTIFY
#include <libnotify/notify.h>
#endif
#if defined(HAS_RGB_LED)
#include "AmbientLightingThread.h"
uint8_t red = 0;
@@ -418,6 +422,9 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
if (genericShouldAlert) {
LOG_INFO("externalNotificationModule - Generic alert");
setExternalState(0, true);
#if HAS_LIBNOTIFY
portduinoNotify(mp);
#endif
}
if (vibraShouldAlert) {
@@ -531,3 +538,40 @@ int ExternalNotificationModule::handleInputEvent(const InputEvent *event)
}
return 0;
}
#if HAS_LIBNOTIFY
void ExternalNotificationModule::portduinoNotify(const meshtastic_MeshPacket &mp)
{
std::string senderName;
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
if (sender && sender->has_user) {
if (sender->user.long_name[0] != '\0') {
senderName = sender->user.long_name;
} else {
senderName = sender->user.short_name;
}
} else {
senderName = std::to_string(mp.from);
}
std::string notificationSummary = "From: " + senderName;
std::string notificationBody = std::string((char *)mp.decoded.payload.bytes, mp.decoded.payload.size);
if (!notify_is_initted()) {
if (!notify_init("Meshtasticd")) {
LOG_WARN("Failed to initialize libnotify");
return;
}
}
NotifyNotification *notification =
notify_notification_new(notificationSummary.c_str(), notificationBody.c_str(), "org.meshtastic.meshtasticd");
if (notification) {
GError *error = nullptr;
if (!notify_notification_show(notification, &error)) {
LOG_WARN("Failed to show notification: %s", error ? error->message : "unknown error");
if (error)
g_error_free(error);
}
g_object_unref(G_OBJECT(notification));
}
}
#endif
+4
View File
@@ -94,6 +94,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
#if HAS_LIBNOTIFY
void portduinoNotify(const meshtastic_MeshPacket &mp);
#endif
};
extern ExternalNotificationModule *externalNotificationModule;
-3
View File
@@ -339,9 +339,6 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli()
strncpy(takPacket.device_callsign, owner.long_name, sizeof(takPacket.device_callsign) - 1);
takPacket.device_callsign[sizeof(takPacket.device_callsign) - 1] = '\0';
// CoT uid — ATAK drops PLI entities with empty uid; derive stable "!<nodenum>" id.
snprintf(takPacket.uid, sizeof(takPacket.uid), "!%08x", nodeDB->getNodeNum());
// Encode TAKPacketV2 protobuf, leaving room for flags byte prefix
uint8_t protobuf_bytes[sizeof(mp->decoded.payload.bytes) - 1];
size_t proto_size = pb_encode_to_bytes(protobuf_bytes, sizeof(protobuf_bytes), &meshtastic_TAKPacketV2_msg, &takPacket);
+1 -1
View File
@@ -862,7 +862,7 @@ void NimbleBluetooth::setupService()
/// Given a level between 0-100, update the BLE attribute
void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && nimbleBluetooth && nimbleBluetooth->isConnected()) {
if ((config.bluetooth.enabled == true) && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
BatteryCharacteristic->notify();
}
-7
View File
@@ -1,7 +0,0 @@
#pragma once
#if __has_include_next("esp_eth_driver.h")
#include_next "esp_eth_driver.h"
#elif __has_include("esp_eth.h")
#include "esp_eth.h"
#endif
+11 -8
View File
@@ -138,6 +138,7 @@ static const RegionProfile TEST_PROFILE_SPACED = {
/* textThrottle */ 0,
/* positionThrottle */ 0,
/* telemetryThrottle */ 0,
/* overrideSlot */ 0,
};
// A licensed-only profile for testing access control
@@ -150,6 +151,7 @@ static const RegionProfile TEST_PROFILE_LICENSED = {
/* textThrottle */ 5,
/* positionThrottle */ 10,
/* telemetryThrottle */ 10,
/* overrideSlot */ 3,
};
// Turbo-only profile
@@ -162,6 +164,7 @@ static const RegionProfile TEST_PROFILE_TURBO = {
/* textThrottle */ 0,
/* positionThrottle */ 0,
/* telemetryThrottle */ 0,
/* overrideSlot */ 0,
};
// A preset list for the preset-hash override slot test (LONG_FAST + MEDIUM_FAST)
@@ -182,6 +185,7 @@ static const RegionProfile TEST_PROFILE_PRESET_HASH = {
/* textThrottle */ 0,
/* positionThrottle */ 0,
/* telemetryThrottle */ 0,
/* overrideSlot */ OVERRIDE_SLOT_PRESET_HASH,
};
// Standalone test region using US frequencies (26 MHz span → 104 slots at 250 kHz BW)
@@ -196,26 +200,25 @@ static const RegionInfo TEST_REGION_PRESET_HASH = {
false,
&TEST_PROFILE_PRESET_HASH,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST,
OVERRIDE_SLOT_PRESET_HASH,
"TEST_PRESET_HASH",
};
static const RegionInfo testRegions[] = {
// A wide US-like region with spacing + padding
{meshtastic_Config_LoRaConfig_RegionCode_US, 902.0f, 928.0f, 100, 30, false, false, &TEST_PROFILE_SPACED,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, 0, "TEST_US_SPACED"},
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, "TEST_US_SPACED"},
// A narrow band simulating tight EU regulation
{meshtastic_Config_LoRaConfig_RegionCode_EU_868, 869.4f, 869.65f, 10, 14, false, false, &TEST_PROFILE_LICENSED,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW, 3, "TEST_EU_LICENSED"},
meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW, "TEST_EU_LICENSED"},
// A wide-LoRa region with turbo-only presets
{meshtastic_Config_LoRaConfig_RegionCode_LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, &TEST_PROFILE_TURBO,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, 0, "TEST_LORA24_TURBO"},
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, "TEST_LORA24_TURBO"},
// Sentinel — must be last
{meshtastic_Config_LoRaConfig_RegionCode_UNSET, 902.0f, 928.0f, 100, 30, false, false, &TEST_PROFILE_SPACED,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, 0, "TEST_UNSET"},
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, "TEST_UNSET"},
};
static const RegionInfo *getTestRegion(meshtastic_Config_LoRaConfig_RegionCode code)
@@ -253,7 +256,7 @@ static void test_shadowTable_licensedProfileFlagsCorrect()
const RegionInfo *r = getTestRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_868);
TEST_ASSERT_TRUE(r->profile->licensedOnly);
TEST_ASSERT_FALSE(r->profile->audioPermitted);
TEST_ASSERT_EQUAL(3, r->overrideSlot);
TEST_ASSERT_EQUAL(3, r->profile->overrideSlot);
}
static void test_shadowTable_presetCountMatchesExpected()
@@ -316,8 +319,8 @@ static void test_shadowTable_unknownCodeFallsToSentinel()
static void test_shadowTable_presetHashProfileHasCorrectOverrideSlot()
{
TEST_ASSERT_EQUAL(OVERRIDE_SLOT_PRESET_HASH, TEST_REGION_PRESET_HASH.overrideSlot);
TEST_ASSERT_EQUAL(-1, TEST_REGION_PRESET_HASH.overrideSlot);
TEST_ASSERT_EQUAL(OVERRIDE_SLOT_PRESET_HASH, TEST_PROFILE_PRESET_HASH.overrideSlot);
TEST_ASSERT_EQUAL(-1, TEST_PROFILE_PRESET_HASH.overrideSlot);
TEST_ASSERT_EQUAL(2, TEST_REGION_PRESET_HASH.getNumPresets());
}
+11 -4
View File
@@ -2,6 +2,8 @@
extends = portduino_base
build_flags = ${portduino_base.build_flags} -I variants/native/portduino
-I /usr/include
!pkg-config --cflags --libs libnotify
-D HAS_LIBNOTIFY=1
board = cross_platform
board_level = extra
lib_deps =
@@ -27,7 +29,8 @@ build_type = release
lib_deps =
${native_base.lib_deps}
${device-ui_base.lib_deps}
build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunction-sections -fdata-sections -Wl,--gc-sections
build_flags = ${native_base.build_flags}
-Os -lX11 -linput -lxkbcommon -ffunction-sections -fdata-sections -Wl,--gc-sections
-D RAM_SIZE=16384
-D USE_X11=1
-D HAS_TFT=1
@@ -55,7 +58,8 @@ build_type = release
lib_deps =
${native_base.lib_deps}
${device-ui_base.lib_deps}
build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
build_flags = ${native_base.build_flags}
-Os -ffunction-sections -fdata-sections -Wl,--gc-sections
-D RAM_SIZE=8192
-D USE_FRAMEBUFFER=1
-D LV_COLOR_DEPTH=32
@@ -84,7 +88,8 @@ build_type = debug
lib_deps =
${native_base.lib_deps}
${device-ui_base.lib_deps}
build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon
build_flags = ${native_base.build_flags}
-O0 -fsanitize=address -lX11 -linput -lxkbcommon
-D DEBUG_HEAP
-D RAM_SIZE=16384
-D USE_X11=1
@@ -112,7 +117,9 @@ build_src_filter = ${env:native-tft.build_src_filter}
[env:coverage]
extends = env:native
build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
build_flags =
-lgcov --coverage -fprofile-abs-path -fsanitize=address
${env:native.build_flags}
; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
test_testing_command =
${platformio.build_dir}/${this.__env__}/meshtasticd
@@ -1,16 +0,0 @@
[env:seeed_xiao_rp2040]
extends = rp2040_base
board = seeed_xiao_rp2040
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths
build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rp2040/seeed_xiao_rp2040/>
build_flags =
${rp2040_base.build_flags}
-D PRIVATE_HW
-D SEEED_XIAO_RP2040
-I variants/rp2040/seeed_xiao_rp2040
-D DEBUG_RP2040_PORT=Serial
;-D HW_SPI1_DEVICE
debug_build_flags = ${rp2040_base.build_flags}, -g
debug_tool = cmsis-dap ; for e.g. Picotool
@@ -1,10 +0,0 @@
#include <Arduino.h>
// Turn off the green and blue LEDs, which are on by default.
void initVariant()
{
pinMode(PIN_LED_G, OUTPUT);
pinMode(PIN_LED_B, OUTPUT);
digitalWrite(PIN_LED_G, HIGH);
digitalWrite(PIN_LED_B, HIGH);
}
@@ -1,27 +0,0 @@
#define ARDUINO_ARCH_AVR
// #define BUTTON_PIN -1
#define LED_POWER PIN_LED_R
// active low RGB led
#define LED_POWER_ON 0
// no ADC by default
#define BATTERY_PIN -1
// ratio of voltage divider = 3.0 (R1=200k, R2=100k)
#define ADC_MULTIPLIER 3
#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION
#define USE_SX1262
#define LORA_SCK 2
#define LORA_MISO 4
#define LORA_MOSI 3
#define SX126X_CS 6
#define SX126X_DIO1 27
#define SX126X_BUSY 29
#define SX126X_RESET 28
#define SX126X_RXEN 7
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
@@ -1,15 +0,0 @@
[env:seeed_xiao_rp2350]
extends = rp2350_base
board = seeed_xiao_rp2350
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths
build_flags =
${rp2350_base.build_flags}
-D PRIVATE_HW
-I variants/rp2350/seeed_xiao_rp2350
-D DEBUG_RP2040_PORT=Serial
;-D HW_SPI1_DEVICE
debug_build_flags = ${rp2350_base.build_flags}, -g
debug_tool = cmsis-dap ; for e.g. Picotool
@@ -1,26 +0,0 @@
#define ARDUINO_ARCH_AVR
// #define BUTTON_PIN -1
#define LED_POWER PIN_LED
// no ADC by default
#define BATTERY_PIN -1
// ratio of voltage divider = 3.0 (R1=200k, R2=100k)
#define ADC_MULTIPLIER 3
// ADC_RESOLUTION is missing upstream, hardcode it here.
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define USE_SX1262
#define LORA_SCK 2
#define LORA_MISO 4
#define LORA_MOSI 3
#define SX126X_CS 6
#define SX126X_DIO1 27
#define SX126X_BUSY 5
#define SX126X_RESET 28
#define SX126X_RXEN 7
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8