Merge branch 'master' into picomputer-s3

This commit is contained in:
Thomas Göttgens 2023-08-06 16:55:28 +02:00 committed by GitHub
commit 8552cc44b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 556 additions and 71 deletions

View File

@ -6,4 +6,4 @@
"platformio.platformio-ide", "platformio.platformio-ide",
"trunk.io" "trunk.io"
], ],
} }

View File

@ -5,7 +5,9 @@ extends = arduino_base
build_type = debug ; I'm debugging with ICE a lot now build_type = debug ; I'm debugging with ICE a lot now
build_flags = build_flags =
${arduino_base.build_flags} -Wno-unused-variable ${arduino_base.build_flags}
-DSERIAL_BUFFER_SIZE=1024
-Wno-unused-variable
-Isrc/platform/nrf52 -Isrc/platform/nrf52
build_src_filter = build_src_filter =

@ -1 +1 @@
Subproject commit 8e7500278f32a0f8096961843aad9b431916dcb0 Subproject commit c5fa71fbb68b8d4044cb6a6d72f06257ac29dd9c

View File

@ -1,3 +1,13 @@
/**
* @file FSCommon.cpp
* @brief This file contains functions for common filesystem operations such as copying, renaming, listing and deleting files and
* directories.
*
* The functions in this file are used to perform common filesystem operations such as copying, renaming, listing and deleting
* files and directories. These functions are used in the Meshtastic-device project to manage files and directories on the
* device's filesystem.
*
*/
#include "FSCommon.h" #include "FSCommon.h"
#include "configuration.h" #include "configuration.h"
@ -14,6 +24,13 @@ SPIClass SPI1(HSPI);
#endif // HAS_SDCARD #endif // HAS_SDCARD
/**
* @brief Copies a file from one location to another.
*
* @param from The path of the source file.
* @param to The path of the destination file.
* @return true if the file was successfully copied, false otherwise.
*/
bool copyFile(const char *from, const char *to) bool copyFile(const char *from, const char *to)
{ {
#ifdef FSCom #ifdef FSCom
@ -43,6 +60,14 @@ bool copyFile(const char *from, const char *to)
#endif #endif
} }
/**
* Renames a file from pathFrom to pathTo.
*
* @param pathFrom The original path of the file.
* @param pathTo The new path of the file.
*
* @return True if the file was successfully renamed, false otherwise.
*/
bool renameFile(const char *pathFrom, const char *pathTo) bool renameFile(const char *pathFrom, const char *pathTo)
{ {
#ifdef FSCom #ifdef FSCom
@ -59,6 +84,13 @@ bool renameFile(const char *pathFrom, const char *pathTo)
#endif #endif
} }
/**
* Lists the contents of a directory.
*
* @param dirname The name of the directory to list.
* @param levels The number of levels of subdirectories to list.
* @param del Whether or not to delete the contents of the directory after listing.
*/
void listDir(const char *dirname, uint8_t levels, bool del = false) void listDir(const char *dirname, uint8_t levels, bool del = false)
{ {
#ifdef FSCom #ifdef FSCom
@ -154,6 +186,13 @@ void listDir(const char *dirname, uint8_t levels, bool del = false)
#endif #endif
} }
/**
* @brief Removes a directory and all its contents.
*
* This function recursively removes a directory and all its contents, including subdirectories and files.
*
* @param dirname The name of the directory to remove.
*/
void rmDir(const char *dirname) void rmDir(const char *dirname)
{ {
#ifdef FSCom #ifdef FSCom
@ -182,6 +221,9 @@ void fsInit()
#endif #endif
} }
/**
* Initializes the SD card and mounts the file system.
*/
void setupSDCard() void setupSDCard()
{ {
#ifdef HAS_SDCARD #ifdef HAS_SDCARD
@ -212,4 +254,4 @@ void setupSDCard()
LOG_DEBUG("Total space: %llu MB\n", SD.totalBytes() / (1024 * 1024)); LOG_DEBUG("Total space: %llu MB\n", SD.totalBytes() / (1024 * 1024));
LOG_DEBUG("Used space: %llu MB\n", SD.usedBytes() / (1024 * 1024)); LOG_DEBUG("Used space: %llu MB\n", SD.usedBytes() / (1024 * 1024));
#endif #endif
} }

View File

@ -29,6 +29,16 @@ static void IRAM_ATTR onTimer()
(*tCallback)(tParam1, tParam2); (*tCallback)(tParam1, tParam2);
} }
/**
* Schedules a hardware callback function to be executed after a specified delay.
*
* @param callback The function to be executed.
* @param param1 The first parameter to be passed to the function.
* @param param2 The second parameter to be passed to the function.
* @param delayMsec The delay time in milliseconds before the function is executed.
*
* @return True if the function was successfully scheduled, false otherwise.
*/
bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec)
{ {
if (!timer) { if (!timer) {

View File

@ -1,3 +1,15 @@
/**
* @file Power.cpp
* @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
* of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
* Power class is used by the main device class to manage power-related functionality.
*
* The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
* the battery voltage is attached via a voltage-divider to an analog input.
*
* This file is part of the Meshtastic project.
* For more information, see: https://meshtastic.org/
*/
#include "power.h" #include "power.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "PowerFSM.h" #include "PowerFSM.h"
@ -366,6 +378,11 @@ bool Power::analogInit()
#endif #endif
} }
/**
* Initializes the Power class.
*
* @return true if the setup was successful, false otherwise.
*/
bool Power::setup() bool Power::setup()
{ {
bool found = axpChipInit(); bool found = axpChipInit();

View File

@ -1,3 +1,12 @@
/**
* @file PowerFSM.cpp
* @brief Implements the finite state machine for power management.
*
* This file contains the implementation of the finite state machine (FSM) for power management.
* The FSM controls the power states of the device, including SDS (shallow deep sleep), LS (light sleep),
* NB (normal mode), and POWER (powered mode). The FSM also handles transitions between states and
* actions to be taken upon entering or exiting each state.
*/
#include "PowerFSM.h" #include "PowerFSM.h"
#include "GPS.h" #include "GPS.h"
#include "MeshService.h" #include "MeshService.h"
@ -137,7 +146,10 @@ static void nbEnter()
{ {
LOG_DEBUG("Enter state: NB\n"); LOG_DEBUG("Enter state: NB\n");
screen->setOn(false); screen->setOn(false);
#ifdef ARCH_ESP32
// Only ESP32 should turn off bluetooth
setBluetoothEnable(false); setBluetoothEnable(false);
#endif
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE // FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
} }
@ -158,6 +170,8 @@ static void serialEnter()
static void serialExit() static void serialExit()
{ {
// Turn bluetooth back on when we leave serial stream API
setBluetoothEnable(true);
screen->print("Serial disconnected\n"); screen->print("Serial disconnected\n");
} }
@ -242,7 +256,11 @@ void PowerFSM_setup()
// wake timer expired or a packet arrived // wake timer expired or a packet arrived
// if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone) // if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone)
#ifdef ARCH_ESP32
powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
#else // Don't go into a no-bluetooth state on low power platforms
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
#endif
// We need this transition, because we might not transition if we were waiting to enter light-sleep, because when we wake from // We need this transition, because we might not transition if we were waiting to enter light-sleep, because when we wake from
// light sleep we _always_ transition to NB or dark and // light sleep we _always_ transition to NB or dark and

View File

@ -213,8 +213,8 @@ bool GPS::setupGPS()
// _serial_gps->begin(9600); //The baud rate of 9600 has been initialized at the beginning of setupGPS, this line // _serial_gps->begin(9600); //The baud rate of 9600 has been initialized at the beginning of setupGPS, this line
// is the redundant part delay(250); // is the redundant part delay(250);
// Initialize the L76K Chip, use GPS + GLONASS // Initialize the L76K Chip, use GPS + GLONASS + BEIDOU
_serial_gps->write("$PCAS04,5*1C\r\n"); _serial_gps->write("$PCAS04,7*1E\r\n");
delay(250); delay(250);
// only ask for RMC and GGA // only ask for RMC and GGA
_serial_gps->write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n"); _serial_gps->write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n");

View File

@ -107,6 +107,14 @@ bool NMEAGPS::lookForLocation()
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
fixQual = reader.fixQuality(); fixQual = reader.fixQuality();
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
LOG_WARN("Warning, %u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
#endif
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS #ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
fixType = atoi(gsafixtype.value()); // will set to zero if no data fixType = atoi(gsafixtype.value()); // will set to zero if no data
// LOG_DEBUG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType); // LOG_DEBUG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
@ -174,8 +182,10 @@ bool NMEAGPS::lookForLocation()
#endif #endif
// Discard incomplete or erroneous readings // Discard incomplete or erroneous readings
if (reader.hdop.value() == 0) if (reader.hdop.value() == 0) {
LOG_WARN("BOGUS hdop.value() REJECTED: %d\n", reader.hdop.value());
return false; return false;
}
p.latitude_i = toDegInt(loc.lat); p.latitude_i = toDegInt(loc.lat);
p.longitude_i = toDegInt(loc.lng); p.longitude_i = toDegInt(loc.lng);
@ -243,7 +253,8 @@ bool NMEAGPS::hasFlow()
bool NMEAGPS::whileIdle() bool NMEAGPS::whileIdle()
{ {
bool isValid = false; bool isValid = false;
// if (_serial_gps->available() > 0)
// LOG_DEBUG("GPS Bytes Waiting: %u\n", _serial_gps->available());
// First consume any chars that have piled up at the receiver // First consume any chars that have piled up at the receiver
while (_serial_gps->available() > 0) { while (_serial_gps->available() > 0) {
int c = _serial_gps->read(); int c = _serial_gps->read();

View File

@ -13,6 +13,7 @@ class NMEAGPS : public GPS
{ {
TinyGPSPlus reader; TinyGPSPlus reader;
uint8_t fixQual = 0; // fix quality from GPGGA uint8_t fixQual = 0; // fix quality from GPGGA
uint32_t lastChecksumFailCount = 0;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS #ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field // (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
@ -53,4 +54,4 @@ class NMEAGPS : public GPS
virtual bool hasLock() override; virtual bool hasLock() override;
virtual bool hasFlow() override; virtual bool hasFlow() override;
}; };

View File

@ -17,6 +17,10 @@ static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
/**
* Reads the current date and time from the RTC module and updates the system time.
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
*/
void readFromRTC() void readFromRTC()
{ {
struct timeval tv; /* btw settimeofday() is helpful here too*/ struct timeval tv; /* btw settimeofday() is helpful here too*/
@ -83,7 +87,15 @@ void readFromRTC()
#endif #endif
} }
/// If we haven't yet set our RTC this boot, set it from a GPS derived time /**
* Sets the RTC (Real-Time Clock) if the provided time is of higher quality than the current RTC time.
*
* @param q The quality of the provided time.
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
* @return True if the RTC was set, false otherwise.
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv) bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
{ {
static uint32_t lastSetMsec = 0; static uint32_t lastSetMsec = 0;
@ -151,6 +163,13 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
} }
} }
/**
* Sets the RTC time if the provided time is of higher quality than the current RTC time.
*
* @param q The quality of the provided time.
* @param t The time to potentially set the RTC to.
* @return True if the RTC was set to the provided time, false otherwise.
*/
bool perhapsSetRTC(RTCQuality q, struct tm &t) bool perhapsSetRTC(RTCQuality q, struct tm &t)
{ {
/* Convert to unix time /* Convert to unix time
@ -171,11 +190,22 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
} }
} }
/**
* Returns the current time in seconds since the Unix epoch (January 1, 1970).
*
* @return The current time in seconds since the Unix epoch.
*/
uint32_t getTime() uint32_t getTime()
{ {
return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs; return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
} }
/**
* Returns the current time from the RTC if the quality of the time is at least minQuality.
*
* @param minQuality The minimum quality of the RTC time required for it to be considered valid.
* @return The current time from the RTC if it meets the minimum quality requirement, or 0 if the time is not valid.
*/
uint32_t getValidTime(RTCQuality minQuality) uint32_t getValidTime(RTCQuality minQuality)
{ {
return (currentQuality >= minQuality) ? getTime() : 0; return (currentQuality >= minQuality) ? getTime() : 0;

View File

@ -3,7 +3,7 @@
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
/** /**
* An adapter class that allows using the TFT_eSPI library as if it was an OLEDDisplay implementation. * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
* *
* Remaining TODO: * Remaining TODO:
* optimize display() to only draw changed pixels (see other OLED subclasses for examples) * optimize display() to only draw changed pixels (see other OLED subclasses for examples)

View File

@ -103,7 +103,8 @@ static uint16_t displayWidth, displayHeight;
#define SCREEN_WIDTH displayWidth #define SCREEN_WIDTH displayWidth
#define SCREEN_HEIGHT displayHeight #define SCREEN_HEIGHT displayHeight
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16 // Height: 19 #define FONT_SMALL ArialMT_Plain_16 // Height: 19
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 #define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
@ -493,7 +494,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no
{ {
char usersString[20]; char usersString[20];
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x, y + 3, 8, 8, imgUser); display->drawFastImage(x, y + 3, 8, 8, imgUser);
#else #else
display->drawFastImage(x, y, 8, 8, imgUser); display->drawFastImage(x, y, 8, 8, imgUser);
@ -1527,7 +1529,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
if (millis() - storeForwardModule->lastHeartbeat > if (millis() - storeForwardModule->lastHeartbeat >
(storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1); imgQuestionL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
@ -1537,7 +1540,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
imgQuestion); imgQuestion);
#endif #endif
} else { } else {
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1); imgSFL1);
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8,
@ -1549,7 +1553,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
} }
#endif #endif
} else { } else {
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1); imgInfoL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,

View File

@ -217,7 +217,96 @@ class LGFX : public lgfx::LGFX_Device
static LGFX tft; static LGFX tft;
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) #elif defined(ILI9341_DRIVER)
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341 driver chip
#if defined(ILI9341_BACKLIGHT_EN) && !defined(TFT_BL)
#define TFT_BL ILI9341_BACKLIGHT_EN
#endif
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ILI9341 _panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
public:
LGFX(void)
{
{
auto cfg = _bus_instance.config();
// configure SPI
cfg.spi_host = ILI9341_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
cfg.spi_mode = 0;
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
// 80MHz by an integer)
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
cfg.use_lock = true; // Set to true to use transaction locking
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
// SPI_DMA_CH_AUTO=auto setting)
cfg.pin_sclk = TFT_SCLK; // Set SPI SCLK pin number
cfg.pin_mosi = TFT_MOSI; // Set SPI MOSI pin number
cfg.pin_miso = TFT_MISO; // Set SPI MISO pin number (-1 = disable)
cfg.pin_dc = TFT_DC; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(cfg); // applies the set value to the bus.
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
}
{ // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
cfg.pin_cs = TFT_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = TFT_RST; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = TFT_BUSY; // Pin number where BUSY is connected (-1 = disable)
// The following setting values are general initial values for each panel, so please comment out any
// unknown items and try them.
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
cfg.offset_rotation = 0; // Rotation direction value offset 0~7 (4~7 is upside down)
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
cfg.readable = true; // Set to true if data can be read
cfg.invert = false; // Set to true if the light/darkness of the panel is reversed
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
cfg.dlen_16bit =
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
// ST7735 or ILI9163.
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
_panel_instance.config(cfg);
}
// Set the backlight control
{
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
cfg.pin_bl = TFT_BL; // Pin number to which the backlight is connected
cfg.invert = false; // true to invert the brightness of the backlight
// cfg.freq = 44100; // PWM frequency of backlight
// cfg.pwm_channel = 1; // PWM channel number to use
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
}
setPanel(&_panel_instance);
}
};
static LGFX tft;
#elif defined(ST7735_CS)
#include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip #include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip
static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
@ -335,9 +424,9 @@ bool TFTDisplay::connect()
#endif #endif
tft.init(); tft.init();
#if defined(M5STACK) || defined(T_DECK) #if defined(T_DECK)
tft.setRotation(1); // M5Stack/T-Deck have the TFT in landscape tft.setRotation(1); // M5Stack/T-Deck have the TFT in landscape
#elif defined(T_WATCH_S3) #elif defined(M5STACK) || defined(T_WATCH_S3)
tft.setRotation(0); // T-Watch S3 has the TFT in portrait tft.setRotation(0); // T-Watch S3 has the TFT in portrait
#else #else
tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label

View File

@ -3,7 +3,7 @@
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
/** /**
* An adapter class that allows using the TFT_eSPI library as if it was an OLEDDisplay implementation. * An adapter class that allows using the LovyanGFX library as if it was an OLEDDisplay implementation.
* *
* Remaining TODO: * Remaining TODO:
* optimize display() to only draw changed pixels (see other OLED subclasses for examples) * optimize display() to only draw changed pixels (see other OLED subclasses for examples)

View File

@ -14,7 +14,8 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF}; const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF}; const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
const uint8_t imgInfoL1[] PROGMEM = {0xff, 0x01, 0x01, 0x01, 0x1e, 0x7f, 0x1e, 0x01, 0x01, 0x01, 0x01, 0xff}; const uint8_t imgInfoL1[] PROGMEM = {0xff, 0x01, 0x01, 0x01, 0x1e, 0x7f, 0x1e, 0x01, 0x01, 0x01, 0x01, 0xff};

View File

@ -1,8 +1,21 @@
/**
* @file memGet.cpp
* @brief Implementation of MemGet class that provides functions to get memory information.
*
* This file contains the implementation of MemGet class that provides functions to get
* information about free heap, heap size, free psram and psram size. The functions are
* implemented for ESP32 and NRF52 architectures. If the platform does not have heap
* management function implemented, the functions return UINT32_MAX or 0.
*/
#include "memGet.h" #include "memGet.h"
#include "configuration.h" #include "configuration.h"
MemGet memGet; MemGet memGet;
/**
* Returns the amount of free heap memory in bytes.
* @return uint32_t The amount of free heap memory in bytes.
*/
uint32_t MemGet::getFreeHeap() uint32_t MemGet::getFreeHeap()
{ {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -15,6 +28,10 @@ uint32_t MemGet::getFreeHeap()
#endif #endif
} }
/**
* Returns the size of the heap memory in bytes.
* @return uint32_t The size of the heap memory in bytes.
*/
uint32_t MemGet::getHeapSize() uint32_t MemGet::getHeapSize()
{ {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -27,6 +44,11 @@ uint32_t MemGet::getHeapSize()
#endif #endif
} }
/**
* Returns the amount of free psram memory in bytes.
*
* @return The amount of free psram memory in bytes.
*/
uint32_t MemGet::getFreePsram() uint32_t MemGet::getFreePsram()
{ {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -36,6 +58,11 @@ uint32_t MemGet::getFreePsram()
#endif #endif
} }
/**
* @brief Returns the size of the PSRAM memory.
*
* @return uint32_t The size of the PSRAM memory.
*/
uint32_t MemGet::getPsramSize() uint32_t MemGet::getPsramSize()
{ {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -43,4 +70,4 @@ uint32_t MemGet::getPsramSize()
#else #else
return 0; return 0;
#endif #endif
} }

View File

@ -82,8 +82,13 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user && if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
nodeInfoModule) { mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
LOG_DEBUG(
"Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our "
"request for its NodeInfo.\n");
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user &&
nodeInfoModule) {
LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel);
nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel);
} }

View File

@ -109,7 +109,6 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW; config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW;
// Enter super deep sleep soon and stay there not very long // Enter super deep sleep soon and stay there not very long
// radioConfig.preferences.mesh_sds_timeout_secs = 10;
// radioConfig.preferences.sds_secs = 60; // radioConfig.preferences.sds_secs = 60;
} }
@ -211,7 +210,6 @@ void NodeDB::initConfigIntervals()
config.position.position_broadcast_secs = default_broadcast_interval_secs; config.position.position_broadcast_secs = default_broadcast_interval_secs;
config.power.ls_secs = default_ls_secs; config.power.ls_secs = default_ls_secs;
config.power.mesh_sds_timeout_secs = default_mesh_sds_timeout_secs;
config.power.min_wake_secs = default_min_wake_secs; config.power.min_wake_secs = default_min_wake_secs;
config.power.sds_secs = default_sds_secs; config.power.sds_secs = default_sds_secs;
config.power.wait_bluetooth_secs = default_wait_bluetooth_secs; config.power.wait_bluetooth_secs = default_wait_bluetooth_secs;

View File

@ -174,7 +174,6 @@ extern NodeDB nodeDB;
# prefs.position_broadcast_secs = FIXME possibly broadcast only once an hr # prefs.position_broadcast_secs = FIXME possibly broadcast only once an hr
prefs.wait_bluetooth_secs = 1 # Don't stay in bluetooth mode prefs.wait_bluetooth_secs = 1 # Don't stay in bluetooth mode
prefs.mesh_sds_timeout_secs = never
# try to stay in light sleep one full day, then briefly wake and sleep again # try to stay in light sleep one full day, then briefly wake and sleep again
prefs.ls_secs = oneday prefs.ls_secs = oneday
@ -202,7 +201,6 @@ extern NodeDB nodeDB;
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
#define default_wait_bluetooth_secs IF_ROUTER(1, 60) #define default_wait_bluetooth_secs IF_ROUTER(1, 60)
#define default_mesh_sds_timeout_secs IF_ROUTER(NODE_DELAY_FOREVER, 2 * 60 * 60)
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep #define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) #define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
#define default_min_wake_secs 10 #define default_min_wake_secs 10

View File

@ -153,7 +153,12 @@ void Router::setReceivedMessage()
meshtastic_QueueStatus Router::getQueueStatus() meshtastic_QueueStatus Router::getQueueStatus()
{ {
return iface->getQueueStatus(); if (!iface) {
meshtastic_QueueStatus qs;
qs.res = qs.mesh_packet_id = qs.free = qs.maxlen = 0;
return qs;
} else
return iface->getQueueStatus();
} }
ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)

View File

@ -292,7 +292,8 @@ typedef struct _meshtastic_Config_PowerConfig {
The number of seconds for to wait before turning off BLE in No Bluetooth states The number of seconds for to wait before turning off BLE in No Bluetooth states
0 for default of 1 minute */ 0 for default of 1 minute */
uint32_t wait_bluetooth_secs; uint32_t wait_bluetooth_secs;
/* Mesh Super Deep Sleep Timeout Seconds /* Deprecated in 2.1.X
Mesh Super Deep Sleep Timeout Seconds
While in Light Sleep if this value is exceeded we will lower into super deep sleep While in Light Sleep if this value is exceeded we will lower into super deep sleep
for sds_secs (default 1 year) or a button press for sds_secs (default 1 year) or a button press
0 for default of two hours, MAXUINT for disabled */ 0 for default of two hours, MAXUINT for disabled */

View File

@ -18,7 +18,9 @@
#include "graphics/fonts/OLEDDisplayFontsUA.h" #include "graphics/fonts/OLEDDisplayFontsUA.h"
#endif #endif
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16 #define FONT_SMALL ArialMT_Plain_16
#define FONT_MEDIUM ArialMT_Plain_24 #define FONT_MEDIUM ArialMT_Plain_24
@ -59,7 +61,8 @@ CannedMessageModule::CannedMessageModule()
{ {
if (moduleConfig.canned_message.enabled) { if (moduleConfig.canned_message.enabled) {
this->loadProtoForModule(); this->loadProtoForModule();
if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address != CARDKB_ADDR)) { if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address != CARDKB_ADDR) &&
(cardkb_found.address != TDECK_KB_ADDR)) {
LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled\n"); LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled\n");
this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED;
disable(); disable();

View File

@ -1,3 +1,18 @@
/**
* @file ExternalNotificationModule.cpp
* @brief Implementation of the ExternalNotificationModule class.
*
* This file contains the implementation of the ExternalNotificationModule class, which is responsible for handling external
* notifications such as vibration, buzzer, and LED lights. The class provides methods to turn on and off the external
* notification outputs and to play ringtones using PWM buzzer. It also includes default configurations and a runOnce() method to
* handle the module's behavior.
*
* Documentation:
* https://meshtastic.org/docs/settings/moduleconfig/external-notification
*
* @author Jm Casler & Meshtastic Team
* @date [Insert Date]
*/
#include "ExternalNotificationModule.h" #include "ExternalNotificationModule.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
@ -113,6 +128,11 @@ int32_t ExternalNotificationModule::runOnce()
} }
} }
/**
* Sets the external notification on for the specified index.
*
* @param index The index of the external notification to turn on.
*/
void ExternalNotificationModule::setExternalOn(uint8_t index) void ExternalNotificationModule::setExternalOn(uint8_t index)
{ {
externalCurrentState[index] = 1; externalCurrentState[index] = 1;

View File

@ -92,6 +92,9 @@ void setupModules()
#endif #endif
} else { } else {
adminModule = new AdminModule(); adminModule = new AdminModule();
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
traceRouteModule = new TraceRouteModule(); traceRouteModule = new TraceRouteModule();
} }
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra

View File

@ -1,3 +1,13 @@
/**
* @file RangeTestModule.cpp
* @brief Implementation of the RangeTestModule class and RangeTestModuleRadio class.
*
* As a sender, this module sends packets every n seconds with an incremented PacketID.
* As a receiver, this module receives packets from multiple senders and saves them to the Filesystem.
*
* The RangeTestModule class is an OSThread that runs the module.
* The RangeTestModuleRadio class handles sending and receiving packets.
*/
#include "RangeTestModule.h" #include "RangeTestModule.h"
#include "FSCommon.h" #include "FSCommon.h"
#include "MeshService.h" #include "MeshService.h"
@ -10,11 +20,6 @@
#include "gps/GeoCoord.h" #include "gps/GeoCoord.h"
#include <Arduino.h> #include <Arduino.h>
/*
As a sender, I can send packets every n seconds. These packets include an incremented PacketID.
As a receiver, I can receive packets from multiple senders. These packets can be saved to the Filesystem.
*/
RangeTestModule *rangeTestModule; RangeTestModule *rangeTestModule;
RangeTestModuleRadio *rangeTestModuleRadio; RangeTestModuleRadio *rangeTestModuleRadio;
@ -97,6 +102,12 @@ int32_t RangeTestModule::runOnce()
return disable(); return disable();
} }
/**
* Sends a payload to a specified destination node.
*
* @param dest The destination node number.
* @param wantReplies Whether or not to request replies from the destination node.
*/
void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
{ {
meshtastic_MeshPacket *p = allocDataPacket(); meshtastic_MeshPacket *p = allocDataPacket();

View File

@ -86,7 +86,13 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
} }
} }
// For the serial2 port we can't really detect if any client is on the other side, so instead just look for recent messages /**
* @brief Checks if the serial connection is established.
*
* @return true if the serial connection is established, false otherwise.
*
* For the serial2 port we can't really detect if any client is on the other side, so instead just look for recent messages
*/
bool SerialModule::checkIsConnected() bool SerialModule::checkIsConnected()
{ {
uint32_t now = millis(); uint32_t now = millis();
@ -191,6 +197,11 @@ int32_t SerialModule::runOnce()
} }
} }
/**
* Allocates a new mesh packet for use as a reply to a received packet.
*
* @return A pointer to the newly allocated mesh packet.
*/
meshtastic_MeshPacket *SerialModuleRadio::allocReply() meshtastic_MeshPacket *SerialModuleRadio::allocReply()
{ {
auto reply = allocDataPacket(); // Allocate a packet for sending auto reply = allocDataPacket(); // Allocate a packet for sending
@ -198,6 +209,12 @@ meshtastic_MeshPacket *SerialModuleRadio::allocReply()
return reply; return reply;
} }
/**
* Sends a payload to a specified destination node.
*
* @param dest The destination node number.
* @param wantReplies Whether or not to request replies from the destination node.
*/
void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies) void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
{ {
meshtastic_Channel *ch = (boundChannel != NULL) ? &channels.getByName(boundChannel) : NULL; meshtastic_Channel *ch = (boundChannel != NULL) ? &channels.getByName(boundChannel) : NULL;
@ -216,6 +233,12 @@ void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
service.sendToMesh(p); service.sendToMesh(p);
} }
/**
* Handle a received mesh packet.
*
* @param mp The received mesh packet.
* @return The processed message.
*/
ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp)
{ {
if (moduleConfig.serial.enabled) { if (moduleConfig.serial.enabled) {
@ -277,6 +300,11 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
return ProcessMessage::CONTINUE; // Let others look at this message also if they want return ProcessMessage::CONTINUE; // Let others look at this message also if they want
} }
/**
* @brief Returns the baud rate of the serial module from the module configuration.
*
* @return uint32_t The baud rate of the serial module.
*/
uint32_t SerialModule::getBaudRate() uint32_t SerialModule::getBaudRate()
{ {
if (moduleConfig.serial.baud == meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_110) { if (moduleConfig.serial.baud == meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_110) {

View File

@ -17,7 +17,8 @@ int32_t DeviceTelemetryModule::runOnce()
uint32_t now = millis(); uint32_t now = millis();
if (((lastSentToMesh == 0) || if (((lastSentToMesh == 0) ||
((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) &&
airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil()) { airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil() &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
sendTelemetry(); sendTelemetry();
lastSentToMesh = now; lastSentToMesh = now;
} else if (service.isToPhoneQueueEmpty()) { } else if (service.isToPhoneQueueEmpty()) {
@ -30,6 +31,10 @@ int32_t DeviceTelemetryModule::runOnce()
bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{ {
// Don't worry about storing telemetry in NodeDB if we're a repeater
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
return false;
if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) { if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
#ifdef DEBUG_PORT #ifdef DEBUG_PORT
const char *sender = getSenderShortName(mp); const char *sender = getSenderShortName(mp);
@ -43,7 +48,19 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
return false; // Let others look at this message also if they want return false; // Let others look at this message also if they want
} }
bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) meshtastic_MeshPacket *DeviceTelemetryModule::allocReply()
{
if (ignoreRequest) {
return NULL;
}
LOG_INFO("Device telemetry replying to request\n");
meshtastic_Telemetry telemetry = getDeviceTelemetry();
return allocDataProtobuf(telemetry);
}
meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
{ {
meshtastic_Telemetry t; meshtastic_Telemetry t;
@ -60,16 +77,22 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent();
t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0; t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f\n", return t;
t.variant.device_metrics.air_util_tx, t.variant.device_metrics.channel_utilization, }
t.variant.device_metrics.battery_level, t.variant.device_metrics.voltage);
meshtastic_MeshPacket *p = allocDataProtobuf(t); bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
meshtastic_Telemetry telemetry = getDeviceTelemetry();
LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f\n",
telemetry.variant.device_metrics.air_util_tx, telemetry.variant.device_metrics.channel_utilization,
telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage);
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
p->to = dest; p->to = dest;
p->decoded.want_response = false; p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_MIN; p->priority = meshtastic_MeshPacket_Priority_MIN;
nodeDB.updateTelemetry(nodeDB.getNodeNum(), t, RX_SRC_LOCAL); nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL);
if (phoneOnly) { if (phoneOnly) {
LOG_INFO("Sending packet to phone\n"); LOG_INFO("Sending packet to phone\n");
service.sendToPhone(p); service.sendToPhone(p);

View File

@ -21,6 +21,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it @return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/ */
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override; virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
virtual meshtastic_MeshPacket *allocReply() override;
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
/** /**
* Send our Telemetry into the mesh * Send our Telemetry into the mesh
@ -28,6 +29,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool phoneOnly = false); bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool phoneOnly = false);
private: private:
meshtastic_Telemetry getDeviceTelemetry();
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0; uint32_t lastSentToMesh = 0;
}; };

View File

@ -31,7 +31,9 @@ SHT31Sensor sht31Sensor;
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
#ifdef USE_EINK #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16 #define FONT_SMALL ArialMT_Plain_16
#define FONT_MEDIUM ArialMT_Plain_24 #define FONT_MEDIUM ArialMT_Plain_24

View File

@ -48,7 +48,9 @@ AudioModule *audioModule;
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
#endif #endif
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16 #define FONT_SMALL ArialMT_Plain_16
#define FONT_MEDIUM ArialMT_Plain_24 #define FONT_MEDIUM ArialMT_Plain_24

View File

@ -1,3 +1,17 @@
/**
* @file StoreForwardModule.cpp
* @brief Implementation of the StoreForwardModule class.
*
* This file contains the implementation of the StoreForwardModule class, which is responsible for managing the store and forward
* functionality of the Meshtastic device. The class provides methods for sending and receiving messages, as well as managing the
* message history queue. It also initializes and manages the data structures used for storing the message history.
*
* The StoreForwardModule class is used by the MeshService class to provide store and forward functionality to the Meshtastic
* device.
*
* @author Jm Casler
* @date [Insert Date]
*/
#include "StoreForwardModule.h" #include "StoreForwardModule.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
@ -52,9 +66,9 @@ int32_t StoreForwardModule::runOnce()
return disable(); return disable();
} }
/* /**
Create our data structure in the PSRAM. * Populates the PSRAM with data to be sent later when a device is out of range.
*/ */
void StoreForwardModule::populatePSRAM() void StoreForwardModule::populatePSRAM()
{ {
/* /*
@ -82,6 +96,12 @@ void StoreForwardModule::populatePSRAM()
LOG_DEBUG("*** numberOfPackets for packetHistory - %u\n", numberOfPackets); LOG_DEBUG("*** numberOfPackets for packetHistory - %u\n", numberOfPackets);
} }
/**
* Sends messages from the message history to the specified recipient.
*
* @param msAgo The number of milliseconds ago from which to start sending messages.
* @param to The recipient ID to send the messages to.
*/
void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to)
{ {
uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to); uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to);
@ -101,6 +121,13 @@ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to)
storeForwardModule->sendMessage(to, sf); storeForwardModule->sendMessage(to, sf);
} }
/**
* Creates a new history queue with messages that were received within the specified time frame.
*
* @param msAgo The number of milliseconds ago to start the history queue.
* @param to The maximum number of messages to include in the history queue.
* @return The ID of the newly created history queue.
*/
uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to) uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
{ {
@ -141,6 +168,11 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
return this->packetHistoryTXQueue_size; return this->packetHistoryTXQueue_size;
} }
/**
* Adds a mesh packet to the history buffer for store-and-forward functionality.
*
* @param mp The mesh packet to add to the history buffer.
*/
void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
{ {
const auto &p = mp.decoded; const auto &p = mp.decoded;
@ -162,6 +194,12 @@ meshtastic_MeshPacket *StoreForwardModule::allocReply()
return reply; return reply;
} }
/**
* Sends a payload to a specified destination node using the store and forward mechanism.
*
* @param dest The destination node number.
* @param packetHistory_index The index of the packet in the packet history buffer.
*/
void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index) void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index)
{ {
LOG_INFO("*** Sending S&F Payload\n"); LOG_INFO("*** Sending S&F Payload\n");
@ -183,6 +221,12 @@ void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index)
service.sendToMesh(p); service.sendToMesh(p);
} }
/**
* Sends a message to a specified destination node using the store and forward protocol.
*
* @param dest The destination node number.
* @param payload The message payload to be sent.
*/
void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward &payload) void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward &payload)
{ {
meshtastic_MeshPacket *p = allocDataProtobuf(payload); meshtastic_MeshPacket *p = allocDataProtobuf(payload);
@ -203,6 +247,12 @@ void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward &p
service.sendToMesh(p); service.sendToMesh(p);
} }
/**
* Sends a store-and-forward message to the specified destination node.
*
* @param dest The destination node number.
* @param rr The store-and-forward request/response message to send.
*/
void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward_RequestResponse rr) void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward_RequestResponse rr)
{ {
// Craft an empty response, save some bytes in flash // Craft an empty response, save some bytes in flash
@ -211,6 +261,11 @@ void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward_Re
storeForwardModule->sendMessage(dest, sf); storeForwardModule->sendMessage(dest, sf);
} }
/**
* Sends statistics about the store and forward module to the specified node.
*
* @param to The node ID to send the statistics to.
*/
void StoreForwardModule::statsSend(uint32_t to) void StoreForwardModule::statsSend(uint32_t to)
{ {
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
@ -231,6 +286,12 @@ void StoreForwardModule::statsSend(uint32_t to)
storeForwardModule->sendMessage(to, sf); storeForwardModule->sendMessage(to, sf);
} }
/**
* Handles a received mesh packet, potentially storing it for later forwarding.
*
* @param mp The received mesh packet.
* @return A `ProcessMessage` indicating whether the packet was successfully handled.
*/
ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &mp)
{ {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -287,6 +348,13 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
return ProcessMessage::CONTINUE; // Let others look at this message also if they want return ProcessMessage::CONTINUE; // Let others look at this message also if they want
} }
/**
* Handles a received protobuf message for the Store and Forward module.
*
* @param mp The received MeshPacket to handle.
* @param p A pointer to the StoreAndForward object.
* @return True if the message was successfully handled, false otherwise.
*/
bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreAndForward *p) bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreAndForward *p)
{ {
if (!moduleConfig.store_forward.enabled) { if (!moduleConfig.store_forward.enabled) {
@ -488,4 +556,4 @@ StoreForwardModule::StoreForwardModule()
disable(); disable();
} }
#endif #endif
} }

View File

@ -1,3 +1,21 @@
/**
* @file xmodem.cpp
* @brief Implementation of XMODEM protocol for Meshtastic devices.
*
* This file contains the implementation of the XMODEM protocol for Meshtastic devices. It is based on the XMODEM implementation
* by Georges Menie (www.menie.org) and has been adapted for protobuf encapsulation.
*
* The XMODEM protocol is used for reliable transmission of binary data over a serial connection. This implementation supports
* both sending and receiving of data.
*
* The XModemAdapter class provides the main functionality for the protocol, including CRC calculation, packet handling, and
* control signal sending.
*
* @copyright Copyright (c) 2001-2019 Georges Menie
* @author
* @author
* @date
*/
/*********************************************************************************************************************** /***********************************************************************************************************************
* based on XMODEM implementation by Georges Menie (www.menie.org) * based on XMODEM implementation by Georges Menie (www.menie.org)
*********************************************************************************************************************** ***********************************************************************************************************************
@ -36,6 +54,13 @@ XModemAdapter xModem;
XModemAdapter::XModemAdapter() {} XModemAdapter::XModemAdapter() {}
/**
* Calculates the CRC-16 CCITT checksum of the given buffer.
*
* @param buffer The buffer to calculate the checksum for.
* @param length The length of the buffer.
* @return The calculated checksum.
*/
unsigned short XModemAdapter::crc16_ccitt(const pb_byte_t *buffer, int length) unsigned short XModemAdapter::crc16_ccitt(const pb_byte_t *buffer, int length)
{ {
unsigned short crc16 = 0; unsigned short crc16 = 0;
@ -52,6 +77,15 @@ unsigned short XModemAdapter::crc16_ccitt(const pb_byte_t *buffer, int length)
return crc16; return crc16;
} }
/**
* Calculates the checksum of the given buffer and compares it to the given
* expected checksum. Returns 1 if the checksums match, 0 otherwise.
*
* @param buf The buffer to calculate the checksum of.
* @param sz The size of the buffer.
* @param tcrc The expected checksum.
* @return 1 if the checksums match, 0 otherwise.
*/
int XModemAdapter::check(const pb_byte_t *buf, int sz, unsigned short tcrc) int XModemAdapter::check(const pb_byte_t *buf, int sz, unsigned short tcrc)
{ {
return crc16_ccitt(buf, sz) == tcrc; return crc16_ccitt(buf, sz) == tcrc;
@ -214,4 +248,4 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket)
// Unknown control character // Unknown control character
break; break;
} }
} }

View File

@ -8,4 +8,4 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@^1.1.7 lovyan03/LovyanGFX@^1.1.8

View File

@ -21,6 +21,7 @@
#define TFT_OFFSET_Y 0 #define TFT_OFFSET_Y 0
#define VTFT_CTRL 46 // Heltec Tracker needs this pulled low for TFT #define VTFT_CTRL 46 // Heltec Tracker needs this pulled low for TFT
#define SCREEN_TRANSITION_FRAMERATE 1 // fps #define SCREEN_TRANSITION_FRAMERATE 1 // fps
#define DISPLAY_FORCE_SMALL_FONTS
#define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost
#define BUTTON_PIN 0 #define BUTTON_PIN 0

View File

@ -2,8 +2,6 @@
extends = esp32_base extends = esp32_base
board = m5stack-core-esp32 board = m5stack-core-esp32
board_level = extra board_level = extra
upload_port = COM8
monitor_port = COM8
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
build_src_filter = build_src_filter =
${esp32_base.build_src_filter} ${esp32_base.build_src_filter}
@ -28,4 +26,4 @@ lib_ignore =
m5stack-core m5stack-core
lib_deps = lib_deps =
${esp32_base.lib_deps} ${esp32_base.lib_deps}
bodmer/TFT_eSPI@^2.4.76 lovyan03/LovyanGFX@^1.1.8

View File

@ -34,8 +34,13 @@
#define GPS_RX_PIN 16 #define GPS_RX_PIN 16
#define GPS_TX_PIN 17 #define GPS_TX_PIN 17
// Define if screen should be mirrored left to right #define TFT_HEIGHT 240
#define SCREEN_ROTATE #define TFT_WIDTH 320
#define TFT_OFFSET_X 0
#define TFT_OFFSET_Y 0
#define TFT_BUSY -1
// LCD screens are slow, so slowdown the wipe so it looks better // LCD screens are slow, so slowdown the wipe so it looks better
#define SCREEN_TRANSITION_FRAMERATE 1 // fps #define SCREEN_TRANSITION_FRAMERATE 1 // fps
#define ILI9341_SPI_HOST VSPI_HOST // VSPI_HOST or HSPI_HOST

View File

@ -39,7 +39,9 @@
#undef RF95_MOSI #undef RF95_MOSI
#undef RF95_NSS #undef RF95_NSS
#define USE_RF95 #define USE_RF95
//#define USE_SX1280
#ifdef USE_RF95
#define RF95_SCK 18 #define RF95_SCK 18
#define RF95_MISO 34 #define RF95_MISO 34
#define RF95_MOSI 23 #define RF95_MOSI 23
@ -48,6 +50,22 @@
#define LORA_RESET 26 #define LORA_RESET 26
#define LORA_DIO1 RADIOLIB_NC #define LORA_DIO1 RADIOLIB_NC
#define LORA_DIO2 RADIOLIB_NC #define LORA_DIO2 RADIOLIB_NC
#endif
#ifdef USE_SX1280
#define RF95_SCK 18
#define RF95_MISO 34
#define RF95_MOSI 23
#define RF95_NSS 14
#define LORA_RESET 26
#define LORA_DIO1 25
#define LORA_DIO2 13
#define SX128X_CS RF95_NSS
#define SX128X_DIO1 LORA_DIO1
#define SX128X_BUSY LORA_DIO2
#define SX128X_RESET LORA_RESET
#define SX128X_MAX_POWER 13 // 10
#endif
#define USE_EINK #define USE_EINK
// https://docs.m5stack.com/en/core/coreink // https://docs.m5stack.com/en/core/coreink
@ -59,3 +77,18 @@
#define PIN_EINK_RES -1 // Connected but not needed #define PIN_EINK_RES -1 // Connected but not needed
#define PIN_EINK_SCLK 18 // EPD_SCLK #define PIN_EINK_SCLK 18 // EPD_SCLK
#define PIN_EINK_MOSI 23 // EPD_MOSI #define PIN_EINK_MOSI 23 // EPD_MOSI
#define BATTERY_PIN 35
#define ADC_CHANNEL ADC1_GPIO35_CHANNEL
// https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/schematic/Core/m5paper/M5_PAPER_SCH.pdf
// https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58
// VBAT
// |
// R83 (3K)
// +
// R86 (11K)
// |
// GND
// https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58
#define ADC_MULTIPLIER 5 // Just a guess for now... more detailed getBatVoltage above
// https://embeddedexplorer.com/esp32-adc-esp-idf-tutorial/

View File

@ -4,10 +4,6 @@ extends = nrf52840_base
board = nano-g2-ultra board = nano-g2-ultra
debug_tool = jlink debug_tool = jlink
# add our variants files to the include and src paths
# define build flags for the TFT_eSPI library - NOTE: WE NOT LONGER USE TFT_eSPI, it was for an earlier version of the TTGO eink screens
# -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_ULTRA build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_ULTRA
-L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard"
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra> build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra>

View File

@ -114,9 +114,8 @@ External serial flash W25Q16JV_IQ
#define SX126X_CS (32 + 13) // FIXME - we really should define LORA_CS instead #define SX126X_CS (32 + 13) // FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 (32 + 10) #define SX126X_DIO1 (32 + 10)
// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching
//#define SX1262_DIO3 \ //#define SX1262_DIO3 (0 + 21)
(0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main CPU?
// CPU?
#define SX126X_BUSY (32 + 11) #define SX126X_BUSY (32 + 11)
#define SX126X_RESET (32 + 15) #define SX126X_RESET (32 + 15)
#define SX126X_E22 // DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 #define SX126X_E22 // DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3

View File

@ -11,4 +11,4 @@ build_flags = ${esp32_base.build_flags}
-Ivariants/t-deck -Ivariants/t-deck
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@^1.1.7 lovyan03/LovyanGFX@^1.1.8

View File

@ -4,9 +4,6 @@ extends = nrf52840_base
board = t-echo board = t-echo
debug_tool = jlink debug_tool = jlink
# add our variants files to the include and src paths
# define build flags for the TFT_eSPI library - NOTE: WE NOT LONGER USE TFT_eSPI, it was for an earlier version of the TTGO eink screens
# -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo
-L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard"

View File

@ -12,6 +12,6 @@ build_flags = ${esp32_base.build_flags}
-DPCF8563_RTC=0x51 -DPCF8563_RTC=0x51
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@^1.1.7 lovyan03/LovyanGFX@^1.1.8
lewisxhe/PCF8563_Library@1.0.1 lewisxhe/PCF8563_Library@1.0.1
adafruit/Adafruit DRV2605 Library@^1.2.2 adafruit/Adafruit DRV2605 Library@^1.2.2

View File

@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 2 major = 2
minor = 1 minor = 1
build = 22 build = 23