diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 5c1cf4c21..e77b4a261 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -32,7 +32,7 @@ jobs: - board: meshtastic-diy-v1 - board: rak4631 - board: t-echo - - board: station-g1 + - board: station-g2 - board: m5stack-coreink - board: tbeam-s3-core - board: tlora-t3s3-v1 @@ -93,12 +93,14 @@ jobs: - board: heltec-wsl-v3 - board: heltec-wireless-tracker - board: heltec-wireless-tracker-V1-0 - - board: heltec-wireless-paper + - board: heltec-wireless-paper-v1_0 + - board: heltec-wireless-paper #v1.1 - board: tbeam-s3-core - board: tlora-t3s3-v1 - board: t-watch-s3 - board: t-deck - board: picomputer-s3 + - board: station-g2 uses: ./.github/workflows/build_esp32_s3.yml with: board: ${{ matrix.board }} diff --git a/.trunk/.gitignore b/.trunk/.gitignore index 1e2465290..15966d087 100644 --- a/.trunk/.gitignore +++ b/.trunk/.gitignore @@ -6,3 +6,4 @@ plugins user_trunk.yaml user.yaml +tmp diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 81a35f8f1..af7d3d21d 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,34 +1,36 @@ version: 0.1 cli: - version: 1.17.2 + version: 1.20.1 plugins: sources: - id: trunk - ref: v1.3.0 + ref: v1.4.3 uri: https://github.com/trunk-io/plugins lint: enabled: - - bandit@1.7.5 - - checkov@3.1.9 - - terrascan@1.18.5 - - trivy@0.47.0 + - trufflehog@3.68.2 + - yamllint@1.35.1 + - bandit@1.7.7 + - checkov@3.2.26 + - terrascan@1.18.11 + - trivy@0.49.1 #- trufflehog@3.63.2-rc0 - taplo@0.8.1 - - ruff@0.1.6 - - isort@5.12.0 - - markdownlint@0.37.0 + - ruff@0.2.2 + - isort@5.13.2 + - markdownlint@0.39.0 - oxipng@9.0.0 - - svgo@3.0.5 - - actionlint@1.6.26 - - flake8@6.1.0 + - svgo@3.2.0 + - actionlint@1.6.27 + - flake8@7.0.0 - hadolint@2.12.0 - shfmt@3.6.0 - shellcheck@0.9.0 - - black@23.9.1 + - black@24.2.0 - git-diff-check - - gitleaks@8.18.1 + - gitleaks@8.18.2 - clang-format@16.0.3 - - prettier@3.1.0 + - prettier@3.2.5 runtimes: enabled: - python@3.10.8 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 783791f0b..4fc84fa78 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,4 +6,4 @@ "platformio.platformio-ide", "trunk.io" ], -} \ No newline at end of file +} diff --git a/boards/station-g2.json b/boards/station-g2.json new file mode 100755 index 000000000..871f067aa --- /dev/null +++ b/boards/station-g2.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "station-g2" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "BQ Station G2", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://wiki.uniteng.com/en/meshtastic/station-g2", + "vendor": "BQ Consulting" +} diff --git a/protobufs b/protobufs index 24edea644..62b7d8b88 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 24edea64429de4474c00d09990ef4c496614dc5d +Subproject commit 62b7d8b884d70aed5ff18c3b0e228095eeb48de2 diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index bac3899bb..c359e4c12 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -245,6 +245,7 @@ Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); + bool isInfrastructureRole = isRouter || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER; bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; @@ -357,10 +358,10 @@ void PowerFSM_setup() // Don't add power saving transitions if we are a power saving tracker or sensor. Sleep will be initiatiated through the // modules if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) { - powerFSM.add_timed_transition(&stateNB, &stateLS, + powerFSM.add_timed_transition(&stateNB, isInfrastructureRole ? &stateSDS : &stateLS, getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateLS, + powerFSM.add_timed_transition(&stateDARK, isInfrastructureRole ? &stateSDS : &stateLS, getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, "Bluetooth timeout"); } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 03bda2cd9..51e2a6f37 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -255,19 +255,6 @@ bool GPS::setup() if (!didSerialInit) { #if !defined(GPS_UC6580) -#if defined(RAK4630) && defined(PIN_3V3_EN) - // If we are using the RAK4630 and we have no other peripherals on the I2C bus or module interest in 3V3_S, - // then we can safely set en_gpio turn off power to 3V3 (IO2) to hard sleep the GPS - if (rtc_found.port == ScanI2C::DeviceType::NONE && rgb_found.type == ScanI2C::DeviceType::NONE && - accelerometer_found.port == ScanI2C::DeviceType::NONE && !moduleConfig.detection_sensor.enabled && - !moduleConfig.telemetry.air_quality_enabled && !moduleConfig.telemetry.environment_measurement_enabled && - config.power.device_battery_ina_address == 0 && en_gpio == 0) { - LOG_DEBUG("Since no problematic peripherals or interested modules were found, setting power save GPS_EN to pin %i\n", - PIN_3V3_EN); - en_gpio = PIN_3V3_EN; - } -#endif - if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); @@ -319,7 +306,6 @@ bool GPS::setup() delay(250); _serial_gps->write("$CFGMSG,6,1,0\r\n"); delay(250); - } else if (gnssModel == GNSS_MODEL_UBLOX) { // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) // We need set it because by default it is GPS only, and we want to use GLONASS too @@ -458,7 +444,6 @@ bool GPS::setup() LOG_WARN("Unable to enable NMEA 4.10.\n"); } } - } else { if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6 msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO); @@ -491,13 +476,6 @@ bool GPS::setup() } } - msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); - _serial_gps->write(UBXscratch, msglen); - if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { - LOG_WARN("Unable to save GNSS module configuration.\n"); - } else { - LOG_INFO("GNSS module configuration saved!\n"); - } } else { // LOG_INFO("u-blox M10 hardware found.\n"); delay(1000); @@ -590,6 +568,13 @@ bool GPS::setup() // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. } + msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE); + _serial_gps->write(UBXscratch, msglen); + if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) { + LOG_WARN("Unable to save GNSS module configuration.\n"); + } else { + LOG_INFO("GNSS module configuration saved!\n"); + } } didSerialInit = true; } @@ -642,12 +627,12 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime) #endif #ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones if (on) { - LOG_INFO("Waking GPS"); + LOG_INFO("Waking GPS\n"); pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, 1); return; } else { - LOG_INFO("GPS entering sleep"); + LOG_INFO("GPS entering sleep\n"); // notifyGPSSleep.notifyObservers(NULL); pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, 0); diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index 19a753c02..cb4e69ff2 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -376,14 +376,17 @@ void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double & } /// Ported from my old java code, returns distance in meters along the globe -/// surface (by magic?) +/// surface (by Haversine formula) float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) { - double pk = (180 / 3.14169); - double a1 = lat_a / pk; - double a2 = lng_a / pk; - double b1 = lat_b / pk; - double b2 = lng_b / pk; + // Don't do math if the points are the same + if (lat_a == lat_b && lng_a == lng_b) + return 0.0; + + double a1 = lat_a / DEG_CONVERT; + double a2 = lng_a / DEG_CONVERT; + double b1 = lat_b / DEG_CONVERT; + double b2 = lng_b / DEG_CONVERT; double cos_b1 = cos(b1); double cos_a1 = cos(a1); double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2); diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index 06b11c3de..9f911ed93 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -11,6 +11,7 @@ #define PI 3.1415926535897932384626433832795 #define OLC_CODE_LEN 11 +#define DEG_CONVERT 180 / PI // Helper functions // Raises a number to an exponent, handling negative exponents. diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index d5e3f5263..aee30c7f8 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -2,127 +2,49 @@ #ifdef USE_EINK #include "EInkDisplay2.h" -#include "GxEPD2_BW.h" #include "SPILock.h" #include "main.h" #include -#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) -SPIClass *hspi = NULL; -#endif +/* + The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini + Previously, these macros were defined at the top of this file. -#define COLORED GxEPD_BLACK -#define UNCOLORED GxEPD_WHITE + For archival reasons, note that the following configurations had also been tested during this period: + * ifdef RAK4631 + - 4.2 inch + EINK_DISPLAY_MODEL: GxEPD2_420_M01 + EINK_WIDTH: 300 + EINK_WIDTH: 400 -#if defined(TTGO_T_ECHO) -#define TECHO_DISPLAY_MODEL GxEPD2_154_D67 -#elif defined(RAK4630) + - 2.9 inch + EINK_DISPLAY_MODEL: GxEPD2_290_T5D + EINK_WIDTH: 296 + EINK_HEIGHT: 128 -// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give partial update -// support -#define TECHO_DISPLAY_MODEL GxEPD2_213_BN - -// 4.2 inch 300x400 - GxEPD2_420_M01 -// #define TECHO_DISPLAY_MODEL GxEPD2_420_M01 - -// 2.9 inch 296x128 - GxEPD2_290_T5D -// #define TECHO_DISPLAY_MODEL GxEPD2_290_T5D - -// 1.54 inch 200x200 - GxEPD2_154_M09 -// #define TECHO_DISPLAY_MODEL GxEPD2_154_M09 - -#elif defined(MAKERPYTHON) -// 2.9 inch 296x128 - GxEPD2_290_T5D -#define TECHO_DISPLAY_MODEL GxEPD2_290_T5D - -#elif defined(PCA10059) - -// 4.2 inch 300x400 - GxEPD2_420_M01 -#define TECHO_DISPLAY_MODEL GxEPD2_420_M01 - -#elif defined(M5_COREINK) -// M5Stack CoreInk -// 1.54 inch 200x200 - GxEPD2_154_M09 -#define TECHO_DISPLAY_MODEL GxEPD2_154_M09 - -#elif defined(HELTEC_WIRELESS_PAPER) -// #define TECHO_DISPLAY_MODEL GxEPD2_213_T5D -#define TECHO_DISPLAY_MODEL GxEPD2_213_FC1 - -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) -// 2.13" 122x250 - DEPG0213BNS800 -#define TECHO_DISPLAY_MODEL GxEPD2_213_BN - -#endif - -GxEPD2_BW *adafruitDisplay; + - 1.54 inch + EINK_DISPLAY_MODEL: GxEPD2_154_M09 + EINK_WIDTH: 200 + EINK_HEIGHT: 200 +*/ +// Constructor EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { -#if defined(TTGO_T_ECHO) - setGeometry(GEOMETRY_RAWMODE, 200, 200); -#elif defined(RAK4630) - - // GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - setGeometry(GEOMETRY_RAWMODE, 250, 122); - this->displayBufferSize = 250 * (128 / 8); - // GxEPD2_420_M01 - // setGeometry(GEOMETRY_RAWMODE, 300, 400); - - // GxEPD2_290_T5D - // setGeometry(GEOMETRY_RAWMODE, 296, 128); - - // GxEPD2_154_M09 - // setGeometry(GEOMETRY_RAWMODE, 200, 200); - -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) - - // The display's memory is actually 128px x 250px - // Setting the buffersize manually prevents 122/8 truncating to a 15 byte width - // (Or something like that..) - + // Set dimensions in OLEDDisplay base class this->geometry = GEOMETRY_RAWMODE; - this->displayWidth = 250; - this->displayHeight = 122; - this->displayBufferSize = 250 * (128 / 8); + this->displayWidth = EINK_WIDTH; + this->displayHeight = EINK_HEIGHT; -#elif defined(HELTEC_WIRELESS_PAPER) - // GxEPD2_213_BN - 2.13 inch b/w 250x122 - setGeometry(GEOMETRY_RAWMODE, 250, 122); -#elif defined(MAKERPYTHON) - // GxEPD2_290_T5D - setGeometry(GEOMETRY_RAWMODE, 296, 128); + // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer + uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT); + uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT); + if (shortSide % 8 != 0) + shortSide = (shortSide | 7) + 1; -#elif defined(PCA10059) - - // GxEPD2_420_M01 - setGeometry(GEOMETRY_RAWMODE, 300, 400); - -#elif defined(M5_COREINK) - - // M5Stack_CoreInk 200x200 - // 1.54 inch 200x200 - GxEPD2_154_M09 - setGeometry(GEOMETRY_RAWMODE, EPD_HEIGHT, EPD_WIDTH); -#elif defined(my) - - // GxEPD2_290_T5D - setGeometry(GEOMETRY_RAWMODE, 296, 128); - LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n"); - -#elif defined(ESP32_S3_PICO) - - // GxEPD2_290_T94_V2 - setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT); - LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n"); - -#endif - // setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution - // setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does + this->displayBufferSize = longSide * (shortSide / 8); } -// FIXME quick hack to limit drawing to a very slow rate -uint32_t lastDrawMsec; - /** * Force a display update if we haven't drawn within the specified msecLimit */ @@ -131,13 +53,6 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) // No need to grab this lock because we are on our own SPI bus // concurrency::LockGuard g(spiLock); -#if defined(USE_EINK_DYNAMIC_PARTIAL) - // Decide if update is partial or full - bool continueUpdate = determineRefreshMode(); - if (!continueUpdate) - return false; -#else - uint32_t now = millis(); uint32_t sinceLast = now - lastDrawMsec; @@ -146,53 +61,35 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) else return false; -#endif - // FIXME - only draw bits have changed (use backbuf similar to the other displays) - // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t x = 0; x < displayWidth; x++) { // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); - adafruitDisplay->drawPixel(x, y, isset ? COLORED : UNCOLORED); + adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); } } LOG_DEBUG("Updating E-Paper... "); -#if defined(TTGO_T_ECHO) - adafruitDisplay->nextPage(); -#elif defined(RAK4630) || defined(MAKERPYTHON) - - // RAK14000 2.13 inch b/w 250x122 actually now does support partial updates +#if false + // Currently unused; rescued from commented-out line during a refactor + // Use a meaningful macro here if variant doesn't want fast refresh // Full update mode (slow) - // adafruitDisplay->display(false); // FIXME, use partial update mode - - // Only enable for e-Paper with support for partial updates and comment out above adafruitDisplay->display(false); - // 1.54 inch 200x200 - GxEPD2_154_M09 - // 2.13 inch 250x122 - GxEPD2_213_BN - // 2.9 inch 296x128 - GxEPD2_290_T5D - // 4.2 inch 300x400 - GxEPD2_420_M01 - adafruitDisplay->nextPage(); - -#elif defined(PCA10059) || defined(M5_COREINK) - adafruitDisplay->nextPage(); -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) - adafruitDisplay->nextPage(); -#elif defined(HELTEC_WIRELESS_PAPER) - adafruitDisplay->nextPage(); -#elif defined(ESP32_S3_PICO) - adafruitDisplay->nextPage(); -#elif defined(PRIVATE_HW) || defined(my) + adafruitDisplay->display(false) +#else + // Fast update mode adafruitDisplay->nextPage(); #endif +#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) adafruitDisplay->hibernate(); - LOG_DEBUG("done\n"); +#endif + LOG_DEBUG("done\n"); return true; } @@ -203,15 +100,9 @@ void EInkDisplay::display(void) // at least one forceDisplay() keyframe. This prevents flashing when we should the critical // bootscreen (that we want to look nice) -#ifdef USE_EINK_DYNAMIC_PARTIAL - lowPriority(); - forceDisplay(); - highPriority(); -#else if (lastDrawMsec) { forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower } -#endif } // Send a command to the display (low level function) @@ -226,7 +117,7 @@ void EInkDisplay::setDetected(uint8_t detected) (void)detected; } -// Connect to the display +// Connect to the display - variant specific bool EInkDisplay::connect() { LOG_INFO("Doing EInk init\n"); @@ -244,9 +135,9 @@ bool EInkDisplay::connect() #if defined(TTGO_T_ECHO) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(); adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); @@ -254,12 +145,12 @@ bool EInkDisplay::connect() #elif defined(RAK4630) || defined(MAKERPYTHON) { if (eink_found) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); - // RAK14000 2.13 inch b/w 250x122 does actually now support partial updates + // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh adafruitDisplay->setRotation(3); - // Partial update support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2 + // Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2 // adafruitDisplay->setRotation(1); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); } else { @@ -267,7 +158,7 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) { // Is this a normal boot, or a wake from deep sleep? esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause(); @@ -296,243 +187,38 @@ bool EInkDisplay::connect() delay(100); // Create GxEPD2 objects - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + adafruitDisplay = new GxEPD2_BW(*lowLevel); // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); } -#elif defined(HELTEC_WIRELESS_PAPER) - { - hspi = new SPIClass(HSPI); - hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS - delay(100); - pinMode(Vext, OUTPUT); - digitalWrite(Vext, LOW); - delay(100); - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); - adafruitDisplay = new GxEPD2_BW(*lowLevel); - adafruitDisplay->init(); - adafruitDisplay->setRotation(3); - } #elif defined(PCA10059) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); } #elif defined(M5_COREINK) - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(0); - adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); #elif defined(my) || defined(ESP32_S3_PICO) { - auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); - adafruitDisplay = new GxEPD2_BW(*lowLevel); + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); + adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->setRotation(1); - adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } #endif - // adafruitDisplay->setFullWindow(); - // adafruitDisplay->fillScreen(UNCOLORED); - // adafruitDisplay->drawCircle(100, 100, 20, COLORED); - // adafruitDisplay->display(false); return true; } -// Use a mix of full and partial refreshes, to preserve display health -#if defined(USE_EINK_DYNAMIC_PARTIAL) - -// Suggest that subsequent updates should use partial-refresh -void EInkDisplay::highPriority() -{ - isHighPriority = true; -} - -// Suggest that subsequent updates should use full-refresh -void EInkDisplay::lowPriority() -{ - isHighPriority = false; -} - -// Full-refresh is explicitly requested for next one update - no skipping please -void EInkDisplay::demandFullRefresh() -{ - demandingFull = true; -} - -// configure display for partial-refresh -void EInkDisplay::configForPartialRefresh() -{ - // Display-specific code can go here -#if defined(PRIVATE_HW) -#else - // Otherwise: - adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height()); -#endif -} - -// Configure display for full-refresh -void EInkDisplay::configForFullRefresh() -{ - // Display-specific code can go here -#if defined(PRIVATE_HW) -#else - // Otherwise: - adafruitDisplay->setFullWindow(); -#endif -} - -#ifdef EINK_PARTIAL_ERASURE_LIMIT -// Count black pixels in an image. Used for "erasure tracking" -int32_t EInkDisplay::countBlackPixels() -{ - int32_t blackCount = 0; // Signed, to avoid underflow when comparing - for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { - for (uint8_t i = 0; i < 7; i++) { - // Check if each bit is black or white - blackCount += (buffer[b] >> i) & 1; - } - } - return blackCount; -} - -// Evaluate the (rough) amount of black->white pixel change since last full refresh -bool EInkDisplay::tooManyErasures() -{ - // Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes - // but that would require substantially more "code tampering" - - // Get the black pixel stats for this image - int32_t blackCount = countBlackPixels(); - int32_t blackDifference = blackCount - prevBlackCount; - - // Update the running total of "erasures" - black pixels which have become white, since last full-refresh - if (blackDifference < 0) - erasedSinceFull -= blackDifference; - - // Store black pixel count for next time - prevBlackCount = blackCount; - - // Log the running total - help devs setup new boards - LOG_DEBUG("Dynamic Partial: erasedSinceFull=%hu, EINK_PARTIAL_ERASURE_LIMIT=%hu\n", erasedSinceFull, - EINK_PARTIAL_ERASURE_LIMIT); - - // Check if too many pixels have been erased - if (erasedSinceFull > EINK_PARTIAL_ERASURE_LIMIT) - return true; // Too many - else - return false; // Still okay -} -#endif // ifdef EINK_PARTIAL_BRIGHTEN_LIMIT_PX - -bool EInkDisplay::newImageMatchesOld() -{ - uint32_t newImageHash = 0; - - // Generate hash: sum all bytes in the image buffer - for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { - newImageHash += buffer[b]; - } - - // Compare hashes - bool hashMatches = (newImageHash == prevImageHash); - - // Update the cached hash - prevImageHash = newImageHash; - - // Return the comparison result - return hashMatches; -} - -// Change between partial and full refresh config, or skip update, balancing urgency and display health. -bool EInkDisplay::determineRefreshMode() -{ - uint32_t now = millis(); - uint32_t sinceLast = now - lastUpdateMsec; - - // If rate-limiting dropped a high-priority update: - // promote this update, so it runs ASAP - if (missedHighPriorityUpdate) { - isHighPriority = true; - missedHighPriorityUpdate = false; - } - - // Abort: if too soon for a new frame (unless demanding full) - if (!demandingFull && isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) { - LOG_DEBUG("Dynamic Partial: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n"); - missedHighPriorityUpdate = true; - return false; - } - if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) { - return false; - } - - // If demanded full refresh: give it to them - if (demandingFull) - needsFull = true; - - // Check if old image (partial) should be redrawn (as full), for image quality - if (partialRefreshCount > 0 && !isHighPriority) - needsFull = true; - - // If too many partials, require a full-refresh (display health) - if (partialRefreshCount >= partialRefreshLimit) - needsFull = true; - -#ifdef EINK_PARTIAL_ERASURE_LIMIT - // Some displays struggle with erasing black pixels to white, during partial refresh - if (tooManyErasures()) - needsFull = true; -#endif - - // If image matches - // (Block must run, even if full already selected, to store hash for next time) - if (newImageMatchesOld()) { - // If low priority: limit rate - // otherwise, every loop() will run the hash method - if (!isHighPriority) - lastUpdateMsec = now; - - // If update is *not* for display health or image quality, skip it - if (!needsFull) - return false; - } - - // Conditions assessed - not skipping - load the appropriate config - - // If options require a full refresh - if (!isHighPriority || needsFull) { - if (partialRefreshCount > 0) - configForFullRefresh(); - - LOG_DEBUG("Dynamic Partial: conditions met for full-refresh\n"); - partialRefreshCount = 0; - needsFull = false; - demandingFull = false; - erasedSinceFull = 0; // Reset the count for EINK_PARTIAL_ERASURE_LIMIT - tracks ghosting buildup - } - - // If options allow a partial refresh - else { - if (partialRefreshCount == 0) - configForPartialRefresh(); - - LOG_DEBUG("Dynamic Partial: conditions met for partial-refresh\n"); - partialRefreshCount++; - } - - lastUpdateMsec = now; // Mark time for rate limiting - return true; // Instruct calling method to continue with update -} - -#endif // End USE_EINK_DYNAMIC_PARTIAL - #endif diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index aeaddee2d..75770a3bc 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -1,8 +1,11 @@ #pragma once +#ifdef USE_EINK + +#include "GxEPD2_BW.h" #include -#if defined(HELTEC_WIRELESS_PAPER_V1_0) +#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) // Re-enable SPI after deep sleep: rtc_gpio_hold_dis() #include "driver/rtc_io.h" #endif @@ -10,12 +13,15 @@ /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * + * Note: EInkDynamicDisplay derives from this class. + * * Remaining TODO: * optimize display() to only draw changed pixels (see other OLED subclasses for examples) * implement displayOn/displayOff to turn off the TFT device (and backlight) * Use the fast NRF52 SPI API rather than the slow standard arduino version * * turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted? + * Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis() */ class EInkDisplay : public OLEDDisplay { @@ -37,7 +43,7 @@ class EInkDisplay : public OLEDDisplay * * @return true if we did draw the screen */ - bool forceDisplay(uint32_t msecLimit = 1000); + virtual bool forceDisplay(uint32_t msecLimit = 1000); /** * shim to make the abstraction happy @@ -55,80 +61,17 @@ class EInkDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; -#if defined(USE_EINK_DYNAMIC_PARTIAL) - // Full, partial, or skip: balance urgency with display health + // AdafruitGFX display object - instantiated in connect(), variant specific + GxEPD2_BW *adafruitDisplay = NULL; - // Use partial refresh if EITHER: - // * highPriority() was set - // * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS) - - // Use full refresh if EITHER: - // * lowPriority() was set - // * demandFullRefresh() was called - (single shot) - // * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT) - // * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS) - // * (optional) too many "erasures" since full-refresh (black pixels cleared to white) - - // Rate limit if: - // * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS) - // * highPriority(), if multiple partials have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS) - - // Skip update entirely if ALL criteria met: - // * new image matches old image - // * lowPriority() - // * no call to demandFullRefresh() - // * not redrawing for image quality - // * not refreshing for display health - - // ------------------------------------ - - // To implement for your E-Ink display: - // * edit configForPartialRefresh() - // * edit configForFullRefresh() - // * add macros to variant.h, and adjust to taste: - - /* - #define USE_EINK_DYNAMIC_PARTIAL - #define EINK_LOWPRIORITY_LIMIT_SECONDS 30 - #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 - #define EINK_PARTIAL_REPEAT_LIMIT 5 - #define EINK_PARTIAL_ERASURE_LIMIT 300 // optional - */ - - public: - void highPriority(); // Suggest partial refresh - void lowPriority(); // Suggest full refresh - void demandFullRefresh(); // For next update: explicitly request full refresh - - protected: - void configForPartialRefresh(); // Display specific code to select partial refresh mode - void configForFullRefresh(); // Display specific code to return to full refresh mode - bool newImageMatchesOld(); // Is the new update actually different to the last image? - bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update -#ifdef EINK_PARTIAL_ERASURE_LIMIT - int32_t countBlackPixels(); // Calculate the number of black pixels in the new image - bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh? + // If display uses HSPI +#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) + SPIClass *hspi = NULL; #endif - bool isHighPriority = true; // Does the method calling update believe that this is urgent? - bool needsFull = false; // Is a full refresh forced? (display health) - bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc) - bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting? - uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh? - uint32_t lastUpdateMsec = 0; // When did the last update occur? - uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not) - int32_t prevBlackCount = 0; // How many black pixels were in the previous image - uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly) - - // Set in variant.h - const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes - const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes - const uint32_t partialRefreshLimit = EINK_PARTIAL_REPEAT_LIMIT; // Max consecutive partials, before full is triggered - -#else // !USE_EINK_DYNAMIC_PARTIAL - // Tolerate calls to these methods anywhere, just to be safe - void highPriority() {} - void lowPriority() {} - void demandFullRefresh() {} -#endif + private: + // FIXME quick hack to limit drawing to a very slow rate + uint32_t lastDrawMsec = 0; }; + +#endif \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp new file mode 100644 index 000000000..ae1e30fe1 --- /dev/null +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -0,0 +1,384 @@ +#include "configuration.h" + +#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) +#include "EInkDynamicDisplay.h" + +// Constructor +EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) + : EInkDisplay(address, sda, scl, geometry, i2cBus) +{ + // If tracking ghost pixels, grab memory +#ifdef EINK_LIMIT_GHOSTING_PX + dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros +#endif +} + +// Destructor +EInkDynamicDisplay::~EInkDynamicDisplay() +{ + // If we were tracking ghost pixels, free the memory +#ifdef EINK_LIMIT_GHOSTING_PX + delete[] dirtyPixels; +#endif +} + +// Screen requests a BACKGROUND frame +void EInkDynamicDisplay::display() +{ + setFrameFlag(BACKGROUND); + update(); +} + +// Screen requests a RESPONSIVE frame +bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit) +{ + setFrameFlag(RESPONSIVE); + return update(); // (Unutilized) Base class promises to return true if update ran +} + +// Add flag for the next frame +void EInkDynamicDisplay::setFrameFlag(frameFlagTypes flag) +{ + // OR the new flag into the existing flags + this->frameFlags = (frameFlagTypes)(this->frameFlags | flag); +} + +// GxEPD2 code to set fast refresh +void EInkDynamicDisplay::configForFastRefresh() +{ + // Variant-specific code can go here +#if defined(PRIVATE_HW) +#else + // Otherwise: + adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height()); +#endif +} + +// GxEPD2 code to set full refresh +void EInkDynamicDisplay::configForFullRefresh() +{ + // Variant-specific code can go here +#if defined(PRIVATE_HW) +#else + // Otherwise: + adafruitDisplay->setFullWindow(); +#endif +} + +// Run any relevant GxEPD2 code, so next update will use correct refresh type +void EInkDynamicDisplay::applyRefreshMode() +{ + // Change from FULL to FAST + if (currentConfig == FULL && refresh == FAST) { + configForFastRefresh(); + currentConfig = FAST; + } + + // Change from FAST back to FULL + else if (currentConfig == FAST && refresh == FULL) { + configForFullRefresh(); + currentConfig = FULL; + } +} + +// Update fastRefreshCount +void EInkDynamicDisplay::adjustRefreshCounters() +{ + if (refresh == FAST) + fastRefreshCount++; + + else if (refresh == FULL) + fastRefreshCount = 0; +} + +// Trigger the display update by calling base class +bool EInkDynamicDisplay::update() +{ + bool refreshApproved = determineMode(); + if (refreshApproved) + EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system + return refreshApproved; // (Unutilized) Base class promises to return true if update ran +} + +// Assess situation, pick a refresh type +bool EInkDynamicDisplay::determineMode() +{ + checkWasFlooded(); + checkRateLimiting(); + + // If too soon for a new time, abort here + if (refresh == SKIPPED) { + storeAndReset(); + return false; // No refresh + } + + // -- New frame is due -- + + resetRateLimiting(); // Once determineMode() ends, will have to wait again + hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check + LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry + + // Once mode determined, any remaining checks will bypass + checkCosmetic(); + checkDemandingFast(); + checkConsecutiveFastRefreshes(); +#ifdef EINK_LIMIT_GHOSTING_PX + checkExcessiveGhosting(); +#endif + checkFrameMatchesPrevious(); + checkFastRequested(); + + if (refresh == UNSPECIFIED) + LOG_WARN("There was a flaw in the determineMode() logic.\n"); + + // -- Decision has been reached -- + applyRefreshMode(); + adjustRefreshCounters(); + +#ifdef EINK_LIMIT_GHOSTING_PX + // Full refresh clears any ghosting + if (refresh == FULL) + resetGhostPixelTracking(); +#endif + + // Return - call a refresh or not? + if (refresh == SKIPPED) { + storeAndReset(); + return false; // Don't trigger a refresh + } else { + storeAndReset(); + return true; // Do trigger a refresh + } +} + +// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh? +void EInkDynamicDisplay::checkWasFlooded() +{ + if (previousReason == EXCEEDED_RATELIMIT_FAST) { + // If so, allow a BACKGROUND frame to draw as RESPONSIVE + // Because we DID want a RESPONSIVE frame last time, we just didn't get it + setFrameFlag(RESPONSIVE); + } +} + +// Is it too soon for another frame of this type? +void EInkDynamicDisplay::checkRateLimiting() +{ + uint32_t now = millis(); + + // Sanity check: millis() overflow - just let the update run.. + if (previousRunMs > now) + return; + + // Skip update: too soon for BACKGROUND + if (frameFlags == BACKGROUND) { + if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) { + refresh = SKIPPED; + reason = EXCEEDED_RATELIMIT_FULL; + return; + } + } + + // No rate-limit for these special cases + if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST) + return; + + // Skip update: too soon for RESPONSIVE + if (frameFlags & RESPONSIVE) { + if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) { + refresh = SKIPPED; + reason = EXCEEDED_RATELIMIT_FAST; + return; + } + } +} + +// Is this frame COSMETIC (splash screens?) +void EInkDynamicDisplay::checkCosmetic() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // A full refresh is requested for cosmetic purposes: we have a decision + if (frameFlags & COSMETIC) { + refresh = FULL; + reason = FLAGGED_COSMETIC; + LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC\n"); + } +} + +// Is this a one-off special circumstance, where we REALLY want a fast refresh? +void EInkDynamicDisplay::checkDemandingFast() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // A fast refresh is demanded: we have a decision + if (frameFlags & DEMAND_FAST) { + refresh = FAST; + reason = FLAGGED_DEMAND_FAST; + LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST\n"); + } +} + +// Have too many fast-refreshes occured consecutively, since last full refresh? +void EInkDynamicDisplay::checkConsecutiveFastRefreshes() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // If too many FAST refreshes consecutively - force a FULL refresh + if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { + refresh = FULL; + reason = EXCEEDED_LIMIT_FASTREFRESH; + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n"); + } +} + +// Does the new frame match the currently displayed image? +void EInkDynamicDisplay::checkFrameMatchesPrevious() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // If frame is *not* a duplicate, abort the check + if (imageHash != previousImageHash) + return; + +#if !defined(EINK_BACKGROUND_USES_FAST) + // If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality) + if (frameFlags == BACKGROUND && fastRefreshCount > 0) { + refresh = FULL; + reason = REDRAW_WITH_FULL; + LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL\n"); + return; + } +#endif + + // Not redrawn, not COSMETIC, not DEMAND_FAST + refresh = SKIPPED; + reason = FRAME_MATCHED_PREVIOUS; + LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS\n"); +} + +// No objections, we can perform fast-refresh, if desired +void EInkDynamicDisplay::checkFastRequested() +{ + if (refresh != UNSPECIFIED) + return; + + if (frameFlags == BACKGROUND) { +#ifdef EINK_BACKGROUND_USES_FAST + // If we want BACKGROUND to use fast. (FULL only when a limit is hit) + refresh = FAST; + reason = BACKGROUND_USES_FAST; + LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu\n", fastRefreshCount); +#else + // If we do want to use FULL for BACKGROUND updates + refresh = FULL; + reason = FLAGGED_BACKGROUND; + LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n"); +#endif + } + + // Sanity: confirm that we did ask for a RESPONSIVE frame. + if (frameFlags & RESPONSIVE) { + refresh = FAST; + reason = NO_OBJECTIONS; + LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu\n", fastRefreshCount); + } +} + +// Reset the timer used for rate-limiting +void EInkDynamicDisplay::resetRateLimiting() +{ + previousRunMs = millis(); +} + +// Generate a hash of this frame, to compare against previous update +void EInkDynamicDisplay::hashImage() +{ + imageHash = 0; + + // Sum all bytes of the image buffer together + for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) { + imageHash += buffer[b]; + } +} + +// Store the results of determineMode() for future use, and reset for next call +void EInkDynamicDisplay::storeAndReset() +{ + previousRefresh = refresh; + previousReason = reason; + + // Only store image hash if the display will update + if (refresh != SKIPPED) { + previousImageHash = imageHash; + } + + frameFlags = BACKGROUND; + refresh = UNSPECIFIED; +} + +#ifdef EINK_LIMIT_GHOSTING_PX +// Count how many ghost pixels the new image will display +void EInkDynamicDisplay::countGhostPixels() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + // Start a new count + ghostPixelCount = 0; + + // Check new image, bit by bit, for any white pixels at locations marked "dirty" + for (uint16_t i = 0; i < displayBufferSize; i++) { + for (uint8_t bit = 0; bit < 7; bit++) { + + const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh? + const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image? + + // If pixel is (or has been) black since last full-refresh, and now is white: ghosting + if (dirty && shouldBeBlank) + ghostPixelCount++; + + // Update the dirty status for this pixel - will this location become a ghost if set white in future? + if (!dirty && !shouldBeBlank) + dirtyPixels[i] |= (1 << bit); + } + } + + LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount); +} + +// Check if ghost pixel count exceeds the defined limit +void EInkDynamicDisplay::checkExcessiveGhosting() +{ + // If a decision was already reached, don't run the check + if (refresh != UNSPECIFIED) + return; + + countGhostPixels(); + + // If too many ghost pixels, select full refresh + if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) { + refresh = FULL; + reason = EXCEEDED_GHOSTINGLIMIT; + LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT\n"); + } +} + +// Clear the dirty pixels array. Call when full-refresh cleans the display. +void EInkDynamicDisplay::resetGhostPixelTracking() +{ + // Copy the current frame into dirtyPixels[] from the display buffer + memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize); +} +#endif // EINK_LIMIT_GHOSTING_PX + +#endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h new file mode 100644 index 000000000..2880c716b --- /dev/null +++ b/src/graphics/EInkDynamicDisplay.h @@ -0,0 +1,104 @@ +#pragma once + +#include "configuration.h" + +#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) + +#include "EInkDisplay2.h" +#include "GxEPD2_BW.h" + +/* + Derives from the EInkDisplay adapter class. + Accepts suggestions from Screen class about frame type. + Determines which refresh type is most suitable. + (Full, Fast, Skip) +*/ + +class EInkDynamicDisplay : public EInkDisplay +{ + public: + // Constructor + // ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class ) + EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus); + ~EInkDynamicDisplay(); + + // What kind of frame is this + enum frameFlagTypes : uint8_t { + BACKGROUND = (1 << 0), // For frames via display() + RESPONSIVE = (1 << 1), // For frames via forceDisplay() + COSMETIC = (1 << 2), // For splashes + DEMAND_FAST = (1 << 3), // Special case only + }; + void setFrameFlag(frameFlagTypes flag); + + // Set the correct frame flag, then call universal "update()" method + void display() override; + bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused. + + protected: + enum refreshTypes : uint8_t { // Which refresh operation will be used + UNSPECIFIED, + FULL, + FAST, + SKIPPED, + }; + enum reasonTypes : uint8_t { // How was the decision reached + NO_OBJECTIONS, + EXCEEDED_RATELIMIT_FAST, + EXCEEDED_RATELIMIT_FULL, + FLAGGED_COSMETIC, + FLAGGED_DEMAND_FAST, + EXCEEDED_LIMIT_FASTREFRESH, + EXCEEDED_GHOSTINGLIMIT, + FRAME_MATCHED_PREVIOUS, + BACKGROUND_USES_FAST, + FLAGGED_BACKGROUND, + REDRAW_WITH_FULL, + }; + + void configForFastRefresh(); // GxEPD2 code to set fast-refresh + void configForFullRefresh(); // GxEPD2 code to set full-refresh + bool determineMode(); // Assess situation, pick a refresh type + void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type + void adjustRefreshCounters(); // Update fastRefreshCount + bool update(); // Trigger the display update - determine mode, then call base class + + // Checks as part of determineMode() + void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC? + void checkRateLimiting(); // Is this frame too soon? + void checkCosmetic(); // Was the COSMETIC flag set? + void checkDemandingFast(); // Was the DEMAND_FAST flag set? + void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively? + void checkFrameMatchesPrevious(); // Does the new frame match the existing display image? + void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND? + + void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting + void hashImage(); // Generate a hashed version of this frame, to compare against previous update + void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call + + // What we are determining for this frame + frameFlagTypes frameFlags = BACKGROUND; // Frame type(s) - determineMode() input + refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output + reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used + + // What happened last time determineMode() ran + refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome + reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason + + uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting) + uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed! + uint32_t previousImageHash = 0; // Hash of the previous update's frame + uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh? + refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for + + // Optional - track ghosting, pixel by pixel +#ifdef EINK_LIMIT_GHOSTING_PX + void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh + void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit + void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. + uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) + uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use +#endif +}; + +#endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index c0e55ea83..33df78462 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include "error.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" +#include "graphics/ScreenFonts.h" #include "graphics/images.h" #include "input/TouchScreenImpl1.h" #include "main.h" @@ -56,14 +57,6 @@ along with this program. If not, see . #include "platform/portduino/PortduinoGlue.h" #endif -#ifdef OLED_RU -#include "fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "fonts/OLEDDisplayFontsUA.h" -#endif - using namespace meshtastic; /** @todo remove */ namespace graphics @@ -111,31 +104,7 @@ static uint16_t displayWidth, displayHeight; #define SCREEN_WIDTH displayWidth #define SCREEN_HEIGHT displayHeight -#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 -#define FONT_SMALL ArialMT_Plain_16 // Height: 19 -#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 // Height: 13 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) @@ -274,7 +243,7 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i if ((millis() / 10000) % 2) { display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the"); display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,"); - display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Flasher or CLI client."); + display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients."); } else { display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org"); display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information."); @@ -929,9 +898,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); -#elif defined(USE_EINK) +#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) dispdev = new EInkDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) + dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_ST7567) dispdev = new ST7567Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index baee4b140..69e858dd2 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -47,6 +47,7 @@ class Screen #endif #include "EInkDisplay2.h" +#include "EInkDynamicDisplay.h" #include "TFTDisplay.h" #include "TypedQueue.h" #include "commands.h" diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h new file mode 100644 index 000000000..d858add2c --- /dev/null +++ b/src/graphics/ScreenFonts.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.h" +#endif + +#ifdef OLED_UA +#include "graphics/fonts/OLEDDisplayFontsUA.h" +#endif + +#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 +#define FONT_SMALL ArialMT_Plain_16 // Height: 19 +#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#else +#ifdef OLED_RU +#define FONT_SMALL ArialMT_Plain_10_RU +#else +#ifdef OLED_UA +#define FONT_SMALL ArialMT_Plain_10_UA +#else +#define FONT_SMALL ArialMT_Plain_10 // Height: 13 +#endif +#endif +#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#endif + +#define fontHeight(font) ((font)[1] + 1) // height is position 1 + +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index fe1041d3d..2d27c737d 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -88,6 +88,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) channelSettings.psk.size = 1; strncpy(channelSettings.name, "", sizeof(channelSettings.name)); channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel + channelSettings.has_module_settings = true; ch.has_settings = true; ch.role = meshtastic_Channel_Role_PRIMARY; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8cac931ae..770808d68 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -130,6 +130,9 @@ bool NodeDB::factoryReset() LOG_INFO("Performing factory reset!\n"); // first, remove the "/prefs" (this removes most prefs) rmDir("/prefs"); + if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { + LOG_WARN("Could not remove rangetest.csv file\n"); + } // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); installDefaultConfig(); @@ -903,8 +906,8 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) if (!lite) { if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) - screen->print("warning: node_db_lite full! erasing oldest entry\n"); - LOG_INFO("warning: node_db_lite full! erasing oldest entry\n"); + screen->print("Warn: node database full!\nErasing oldest entry\n"); + LOG_INFO("Warn: node database full!\nErasing oldest entry\n"); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b8a33432a..87542f65e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -435,6 +435,7 @@ NodeNum Router::getNodeNum() */ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) { + bool skipHandle = false; // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone // Store a copy of encrypted packet for MQTT @@ -451,8 +452,17 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) else printPacket("handleReceived(REMOTE)", p); + // Neighbor info module is disabled, ignore expensive neighbor info packets + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP && + (!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) { + LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n"); + cancelSending(p->from, p->id); + skipHandle = true; + } + // Publish received message to MQTT if we're not the original transmitter of the packet - if (moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt) + if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); } else { printPacket("packet decoding failed or skipped (no PSK?)", p); @@ -461,7 +471,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) packetPool.release(p_encrypted); // Release the encrypted packet // call modules here - MeshModule::callPlugins(*p, src); + if (!skipHandle) + MeshModule::callPlugins(*p, src); } void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 4e0fdd385..20b1cb31e 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -10,6 +10,8 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo info.snr = lite->snr; info.last_heard = lite->last_heard; info.channel = lite->channel; + info.via_mqtt = lite->via_mqtt; + info.hops_away = lite->hops_away; if (lite->has_position) { info.has_position = true; diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 735644c47..ca4b2176b 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -65,6 +65,10 @@ typedef struct _meshtastic_NodeInfoLite { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; } meshtastic_NodeInfoLite; /* The on-disk saved channels */ @@ -175,13 +179,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} #define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} @@ -200,6 +204,8 @@ extern "C" { #define meshtastic_NodeInfoLite_last_heard_tag 5 #define meshtastic_NodeInfoLite_device_metrics_tag 6 #define meshtastic_NodeInfoLite_channel_tag 7 +#define meshtastic_NodeInfoLite_via_mqtt_tag 8 +#define meshtastic_NodeInfoLite_hops_away_tag 9 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_OEMStore_oem_icon_width_tag 1 @@ -252,7 +258,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User @@ -313,8 +321,8 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_ChannelFile_size 702 -#define meshtastic_DeviceState_size 17062 -#define meshtastic_NodeInfoLite_size 153 +#define meshtastic_DeviceState_size 17571 +#define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_OEMStore_size 3246 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index e8a27d43f..04590210e 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -75,6 +75,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_CANARYONE = 29, /* Waveshare RP2040 LoRa - https://www.waveshare.com/rp2040-lora.htm */ meshtastic_HardwareModel_RP2040_LORA = 30, + /* B&Q Consulting Station G2: https://wiki.uniteng.com/en/meshtastic/station-g2 */ + meshtastic_HardwareModel_STATION_G2 = 31, /* --------------------------------------------------------------------------- Less common/prototype boards listed here (needs one more byte over the air) --------------------------------------------------------------------------- */ @@ -530,11 +532,9 @@ typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t; typedef struct _meshtastic_MeshPacket { /* The sending node number. Note: Our crypto implementation uses this field as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t from; - /* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only - hurts the ble link though. */ + /* The (immediate) destination for this packet */ uint32_t to; /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. If unset, packet was on the primary channel. @@ -558,9 +558,7 @@ typedef struct _meshtastic_MeshPacket { needs to be unique for a few minutes (long enough to last for the length of any ACK or the completion of a mesh broadcast flood). Note: Our crypto implementation uses this id as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only - hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t id; /* The time this message was received by the esp32 (secs since 1970). Note: this field is _never_ sent on the radio link itself (to save space) Times @@ -595,6 +593,9 @@ typedef struct _meshtastic_MeshPacket { meshtastic_MeshPacket_Delayed delayed; /* Describes whether this packet passed via MQTT somewhere along the path it currently took. */ bool via_mqtt; + /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. + When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ + uint8_t hop_start; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -633,6 +634,10 @@ typedef struct _meshtastic_NodeInfo { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; } meshtastic_NodeInfo; /* Unique local debugging info for this node @@ -891,8 +896,8 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -909,8 +914,8 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -990,6 +995,7 @@ extern "C" { #define meshtastic_MeshPacket_rx_rssi_tag 12 #define meshtastic_MeshPacket_delayed_tag 13 #define meshtastic_MeshPacket_via_mqtt_tag 14 +#define meshtastic_MeshPacket_hop_start_tag 15 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -997,6 +1003,8 @@ extern "C" { #define meshtastic_NodeInfo_last_heard_tag 5 #define meshtastic_NodeInfo_device_metrics_tag 6 #define meshtastic_NodeInfo_channel_tag 7 +#define meshtastic_NodeInfo_via_mqtt_tag 8 +#define meshtastic_NodeInfo_hops_away_tag 9 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -1147,7 +1155,8 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 10) \ X(a, STATIC, SINGULAR, UENUM, priority, 11) \ X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \ X(a, STATIC, SINGULAR, UENUM, delayed, 13) \ -X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \ +X(a, STATIC, SINGULAR, UINT32, hop_start, 15) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -1159,7 +1168,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -1312,12 +1323,12 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FromRadio_size 510 #define meshtastic_LogRecord_size 81 -#define meshtastic_MeshPacket_size 323 +#define meshtastic_MeshPacket_size 326 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 270 +#define meshtastic_NodeInfo_size 275 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 94df601d8..abd7c2e54 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -332,6 +332,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { config.lora.tx_enabled = true; initRegion(); + if (myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + } if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 3127b0986..b2b52d1ab 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -18,40 +18,7 @@ #define INPUTBROKER_MATRIX_TYPE 0 #endif -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - -#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 -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" // Remove Canned message screen if no action is taken for some milliseconds #define INACTIVATE_AFTER_MS 20000 diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index fd109b765..2d45868fd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -13,7 +13,6 @@ #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" #include "modules/RemoteHardwareModule.h" -#include "modules/ReplyModule.h" #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" #include "modules/TraceRouteModule.h" @@ -67,7 +66,8 @@ void setupModules() // to a global variable. new RemoteHardwareModule(); - new ReplyModule(); + // Example: Put your module here + // new ReplyModule(); #if HAS_BUTTON || ARCH_PORTDUINO rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index e75822a86..80d1442f8 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -83,6 +83,13 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } nodeDB.updatePosition(getFrom(&mp), p); + if (channels.getByIndex(mp.channel).settings.has_module_settings) { + precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; + } else { + precision = 0; + } return false; // Let others look at this message also if they want } @@ -109,10 +116,24 @@ meshtastic_MeshPacket *PositionModule::allocReply() } localPosition.seq_number++; + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { + LOG_WARN("Skipping position send because lat/lon are zero!\n"); + return nullptr; + } + // lat/lon are unconditionally included - IF AVAILABLE! LOG_DEBUG("Sending location with precision %i\n", precision); - p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision)); - p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision)); + if (precision < 32 && precision > 0) { + p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision)); + p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision)); + + // We want the imprecise position to be the middle of the possible location, not + p.latitude_i += (1 << (31 - precision)); + p.longitude_i += (1 << (31 - precision)); + } else { + p.latitude_i = localPosition.latitude_i; + p.longitude_i = localPosition.longitude_i; + } p.precision_bits = precision; p.time = localPosition.time; @@ -210,7 +231,13 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha service.cancelSending(prevPacketId); // Set's the class precision value for this particular packet - precision = channels.getByIndex(channel).settings.module_settings.position_precision; + if (channels.getByIndex(channel).settings.has_module_settings) { + precision = channels.getByIndex(channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; + } else { + precision = 0; + } meshtastic_MeshPacket *p = allocReply(); if (p == nullptr) { @@ -350,7 +377,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic LOG_DEBUG("currentPosition.latitude_i=%i, currentPosition.longitude_i=%i\n", lastGpsLatitude, lastGpsLongitude); LOG_DEBUG("--------SMART POSITION-----------------------------------\n"); - LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%d, distanceThreshold=% u\n", + LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%f, distanceThreshold=%f\n", abs(distanceTraveledSinceLastSend) >= distanceTravelThreshold, abs(distanceTraveledSinceLastSend), distanceTravelThreshold); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9c7b406e9..e501f17c2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -33,23 +33,7 @@ SHT31Sensor sht31Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#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 -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#include "graphics/ScreenFonts.h" int32_t EnvironmentTelemetryModule::runOnce() { diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 032d7fc27..30628bfd7 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -14,23 +14,7 @@ #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#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 -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#include "graphics/ScreenFonts.h" int32_t PowerTelemetryModule::runOnce() { diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index bc104df11..a10cae954 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -8,14 +8,6 @@ #include "RTC.h" #include "Router.h" -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - /* AudioModule A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project. @@ -48,32 +40,7 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -#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 -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA -#else -#define FONT_SMALL ArialMT_Plain_10 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#endif - -#define fontHeight(font) ((font)[1] + 1) // height is position 1 - -#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) -#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) -#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) +#include "graphics/ScreenFonts.h" void run_codec2(void *parameter) { diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index f3df7ffdf..2182ed124 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -57,7 +57,7 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() int32_t PaxcounterModule::runOnce() { - if (moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled) { + if (isActive()) { if (firstTime) { firstTime = false; LOG_DEBUG( @@ -87,4 +87,24 @@ int32_t PaxcounterModule::runOnce() } } -#endif \ No newline at end of file +#if HAS_SCREEN + +#include "graphics/ScreenFonts.h" + +void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + char buffer[50]; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(x + 0, y + 0, "PAX"); + + libpax_counter_count(&count_from_libpax); + + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, "WiFi: %d\nBLE: %d\nuptime: %ds", + count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); +} +#endif // HAS_SCREEN + +#endif diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index 0aa9be68d..e72f87450 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -23,6 +23,11 @@ class PaxcounterModule : private concurrency::OSThread, public ProtobufModulebusy = false; } } - } else if ((millis() - lastHeartbeat > (heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) { + } else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) && + airTime->isTxAllowedChannelUtil(true)) { lastHeartbeat = millis(); LOG_INFO("*** Sending heartbeat\n"); meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; @@ -141,25 +142,31 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uin LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis()); LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo)); */ - if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { - /* Copy the messages that were received by the router in the last msAgo - to the packetHistoryTXQueue structure. - Client not interested in packets from itself and only in broadcast packets or packets towards it. */ - if (this->packetHistory[i].from != to && - (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = this->packetHistory[i].payload_size; - memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, - meshtastic_Constants_DATA_PAYLOAD_LEN); - this->packetHistoryTXQueue_size++; - *last_request_index = i + 1; // Set to one higher such that we don't send the same message again + if (this->packetHistoryTXQueue_size < this->historyReturnMax) { + if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { + /* Copy the messages that were received by the router in the last msAgo + to the packetHistoryTXQueue structure. + Client not interested in packets from itself and only in broadcast packets or packets towards it. */ + if (this->packetHistory[i].from != to && + (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = + this->packetHistory[i].payload_size; + memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, + meshtastic_Constants_DATA_PAYLOAD_LEN); + this->packetHistoryTXQueue_size++; + *last_request_index = i + 1; // Set to one higher such that we don't send the same message again - LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, - this->packetHistory[i].payload); + LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, + this->packetHistory[i].payload); + } } + } else { + LOG_WARN("*** S&F - Maximum history return reached.\n"); + return this->packetHistoryTXQueue_size; } } return this->packetHistoryTXQueue_size; @@ -174,20 +181,24 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) { const auto &p = mp.decoded; - if (this->packetHistoryCurrent < this->records) { - this->packetHistory[this->packetHistoryCurrent].time = millis(); - this->packetHistory[this->packetHistoryCurrent].to = mp.to; - this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; - this->packetHistory[this->packetHistoryCurrent].from = mp.from; - this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size; - memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); - - this->packetHistoryCurrent++; - this->packetHistoryMax++; - } else { - // TODO: Overwrite the oldest message in the history buffer when it is full. - LOG_WARN("*** S&F - PSRAM Full. Packet is not added to the history.\n"); + if (this->packetHistoryCurrent == this->records) { + LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n"); + this->packetHistoryCurrent = 0; + this->packetHistoryMax = 0; + for (auto &i : lastRequest) { + i.second = 0; // Clear the last request index for each client device + } } + + this->packetHistory[this->packetHistoryCurrent].time = millis(); + this->packetHistory[this->packetHistoryCurrent].to = mp.to; + this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; + this->packetHistory[this->packetHistoryCurrent].from = mp.from; + this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size; + memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); + + this->packetHistoryCurrent++; + this->packetHistoryMax++; } meshtastic_MeshPacket *StoreForwardModule::allocReply() @@ -313,7 +324,8 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; - if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { + if (mp.to == nodeDB.getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && + (p.payload.bytes[2] == 0x00)) { LOG_DEBUG("*** Legacy Request to send\n"); // Send the last 60 minutes of messages. @@ -543,6 +555,8 @@ StoreForwardModule::StoreForwardModule() // send heartbeat advertising? if (moduleConfig.store_forward.heartbeat) this->heartbeat = moduleConfig.store_forward.heartbeat; + else + this->heartbeat = false; // Popupate PSRAM with our data structures. this->populatePSRAM(); diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index cfa9945d5..0d2fb9fce 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -44,7 +44,7 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< StoreForwardModule(); unsigned long lastHeartbeat = 0; - uint32_t heartbeatInterval = default_broadcast_interval_secs; + uint32_t heartbeatInterval = 900; /** Update our local reference of when we last saw that node. diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8c241a302..898607eca 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -351,11 +351,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } +#endif // ARCH_NRF52 } } #endif @@ -450,6 +452,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = this->meshPacketToJson(env->packet); @@ -460,6 +463,7 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } +#endif // ARCH_NRF52 mqttPool.release(env); } } @@ -504,6 +508,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp_decoded); @@ -514,7 +519,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & publish(topicJson.c_str(), jsonString.c_str(), false); } } - +#endif // ARCH_NRF52 } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 4080a7cbc..df2d3e45a 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -16,5 +16,5 @@ class NimbleBluetooth : BluetoothApi void startAdvertising(); }; -void setBluetoothEnable(bool on); -void clearNVS(); +void setBluetoothEnable(bool enable); +void clearNVS(); \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 7fab475f3..703bcefc9 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -133,6 +133,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62 #elif defined(CHATTER_2) #define HW_VENDOR meshtastic_HardwareModel_CHATTER_2 +#elif defined(STATION_G2) +#define HW_VENDOR meshtastic_HardwareModel_STATION_G2 #endif // ----------------------------------------------------------------------------- @@ -155,4 +157,4 @@ #define LORA_CS 18 #endif -#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. +#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index c994eea48..f97f6d121 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -20,21 +20,21 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { if (!nimbleBluetooth) { nimbleBluetooth = new NimbleBluetooth(); } - if (on && !nimbleBluetooth->isActive()) { + if (enable && !nimbleBluetooth->isActive()) { nimbleBluetooth->setup(); - } else if (!on) { + } else if (!enable) { nimbleBluetooth->shutdown(); } } } #else -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index dd81929c8..9a93f5cc6 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -210,8 +210,10 @@ void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); + if (connectionHandle != 0) { + Bluefruit.disconnect(connectionHandle); + } Bluefruit.Advertising.stop(); - Bluefruit.setTxPower(0); // Minimum power } bool NRF52Bluetooth::isConnected() @@ -289,6 +291,14 @@ void NRF52Bluetooth::setup() } } +void NRF52Bluetooth::resumeAdverising() +{ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); +} + /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 193e86cf8..fd27bbf04 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi public: void setup(); void shutdown(); + void resumeAdverising(); void clearBonds(); bool isConnected(); int getRssi(); @@ -17,4 +18,4 @@ class NRF52Bluetooth : BluetoothApi void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); -}; +}; \ No newline at end of file diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 63b014c46..9e8798e37 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -63,28 +63,29 @@ static void initBrownout() // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice } -static bool bleOn = false; static const bool useSoftDevice = true; // Set to false for easier debugging -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { - if (on != bleOn && config.bluetooth.enabled == true) { - if (on) { + if (enable && config.bluetooth.enabled) { + if (!useSoftDevice) { + LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); + } else { if (!nrf52Bluetooth) { - if (!useSoftDevice) - LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); - else { - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); + LOG_DEBUG("Initializing NRF52 Bluetooth\n"); + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); - } + // We delay brownout init until after BLE because BLE starts soft device + initBrownout(); + } else { + nrf52Bluetooth->resumeAdverising(); } - } else if (nrf52Bluetooth) { + } + } else { + if (nrf52Bluetooth) { nrf52Bluetooth->shutdown(); } - bleOn = on; } } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index c8fcc3d13..046509fab 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -20,7 +20,7 @@ std::map settingsStrings; char *configPath = nullptr; // FIXME - move setBluetoothEnable into a HALPlatform class -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2040/main-rp2040.cpp index 3359263e9..283b801f1 100644 --- a/src/platform/rp2040/main-rp2040.cpp +++ b/src/platform/rp2040/main-rp2040.cpp @@ -2,7 +2,7 @@ #include #include -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp index f57928c60..60c3cce10 100644 --- a/src/platform/stm32wl/main-stm32wl.cpp +++ b/src/platform/stm32wl/main-stm32wl.cpp @@ -3,7 +3,7 @@ #include #include -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void playStartMelody() {} @@ -33,4 +33,4 @@ int _gettimeofday(struct timeval *tv, void *tzvp) { return -1; } -} +} \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 0f71ab25b..1afba1173 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -186,7 +186,15 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(skipPreflight); +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + notifySleep.notifyObservers(NULL); + } else { + notifyDeepSleep.notifyObservers(NULL); + } +#else notifyDeepSleep.notifyObservers(NULL); +#endif screen->doDeepSleep(); // datasheet says this will draw only 10ua @@ -240,6 +248,11 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) } #endif +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + enableLoraInterrupt(); + } +#endif cpuDeepSleep(msecToWake); } @@ -294,12 +307,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); #endif #endif -#if defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high -#endif -#ifdef RF95_IRQ - gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high -#endif + enableLoraInterrupt(); #ifdef PMU_IRQ // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills if (pmu_found) @@ -359,4 +367,30 @@ void enableModemSleep() int rv = esp_pm_configure(&esp32_config); LOG_DEBUG("Sleep request result %x\n", rv); } + +bool shouldLoraWake(uint32_t msecToWake) +{ + return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER); +} + +void enableLoraInterrupt() +{ +#if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + rtc_gpio_pulldown_en((gpio_num_t)LORA_DIO1); +#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) + rtc_gpio_pullup_en((gpio_num_t)LORA_RESET); +#endif +#if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) + rtc_gpio_pullup_en((gpio_num_t)LORA_CS); +#endif + // Setup deep sleep with wakeup by external source + esp_sleep_enable_ext0_wakeup((gpio_num_t)LORA_DIO1, RISING); +#elif defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high +#endif +#ifdef RF95_IRQ + gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#endif +} #endif \ No newline at end of file diff --git a/src/sleep.h b/src/sleep.h index a592ad2c1..8d5b9a94f 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -43,4 +43,8 @@ extern Observable notifyDeepSleep; /// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 extern Observable notifyGPSSleep; -void enableModemSleep(); \ No newline at end of file +void enableModemSleep(); +#ifdef ARCH_ESP32 +void enableLoraInterrupt(); +bool shouldLoraWake(uint32_t msecToWake); +#endif \ No newline at end of file diff --git a/src/target_specific.h b/src/target_specific.h index 1e79df510..7404a3720 100644 --- a/src/target_specific.h +++ b/src/target_specific.h @@ -5,6 +5,6 @@ // Functions that are unique to particular target types (esp32, bare, nrf52 etc...) // Enable/disable bluetooth. -void setBluetoothEnable(bool on); +void setBluetoothEnable(bool enable); void getMacAddr(uint8_t *dmac); \ No newline at end of file diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index a937bfa98..b1608770e 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -4,6 +4,9 @@ board = nordic_pca10059 board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 + -DEINK_WIDTH=300 + -DEINK_HEIGHT=400 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840-pca10059-v1> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index 81e2ad995..0f1bf15da 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -136,7 +136,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define USE_SX1262 #define SX126X_CS (0 + 31) // LORA_CS P0.31 #define SX126X_DIO1 (0 + 29) // DIO1 P0.29 -#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 +#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 #define SX126X_RESET (32 + 15) // LORA_RESET P1.15 #define SX126X_TXEN (32 + 13) // TXEN P1.13 NiceRF 868 dont use #define SX126X_RXEN (32 + 10) // RXEN P1.10 NiceRF 868 dont use diff --git a/variants/MakePython_nRF52840_eink/platformio.ini b/variants/MakePython_nRF52840_eink/platformio.ini index 3ac18bcf0..f421466ec 100644 --- a/variants/MakePython_nRF52840_eink/platformio.ini +++ b/variants/MakePython_nRF52840_eink/platformio.ini @@ -10,5 +10,8 @@ lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f zinggjm/GxEPD2@^1.4.9 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 debug_tool = jlink ;upload_port = /dev/ttyACM4 diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32-s3-pico/platformio.ini index f39004c36..ef737d98a 100644 --- a/variants/esp32-s3-pico/platformio.ini +++ b/variants/esp32-s3-pico/platformio.ini @@ -16,9 +16,9 @@ build_flags = ${esp32_base.build_flags} ;-DPRIVATE_HW -Ivariants/esp32-s3-pico -DBOARD_HAS_PSRAM - -DTECHO_DISPLAY_MODEL=GxEPD2_290_T94_V2 - -DEPD_HEIGHT=128 - -DEPD_WIDTH=296 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2 + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 lib_deps = ${esp32s3_base.lib_deps} zinggjm/GxEPD2@^1.5.3 diff --git a/variants/esp32-s3-pico/variant.h b/variants/esp32-s3-pico/variant.h index 9e10183fb..87378d378 100644 --- a/variants/esp32-s3-pico/variant.h +++ b/variants/esp32-s3-pico/variant.h @@ -74,4 +74,4 @@ #define PIN_EINK_DC 33 #define PIN_EINK_RES 42 // 37 //(-1) // cant be MISO Waveshare ??) #define PIN_EINK_SCLK 35 -#define PIN_EINK_MOSI 36 +#define PIN_EINK_MOSI 36 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index dd93b52cb..0abbe085e 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -2,10 +2,21 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = - ${esp32s3_base.build_flags} -D HELTEC_WIRELESS_PAPER -I variants/heltec_wireless_paper + ${esp32s3_base.build_flags} + -I variants/heltec_wireless_paper + -D HELTEC_WIRELESS_PAPER + -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/ixt/GxEPD2#39f325b677713eb04dfcc83b8e402e77523fb8bf + https://github.com/meshtastic/GxEPD2 adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index c2a030ed0..28bc8628a 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,6 +5,8 @@ #define I2C_SCL SCL #define USE_EINK +#define EINK_NO_HIBERNATE + /* * eink display pins */ diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini index 7d7f4eb14..4e5e291e0 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -5,6 +5,15 @@ build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_paper_v1 -D HELTEC_WIRELESS_PAPER_V1_0 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/ diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h index 166b7f30e..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper_v1/variant.h +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -6,13 +6,6 @@ #define USE_EINK -// Settings for Dynamic Partial mode -// Change between partial and full refresh config, or skip update, balancing urgency and display health. -#define USE_EINK_DYNAMIC_PARTIAL -#define EINK_LOWPRIORITY_LIMIT_SECONDS 30 -#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1 -#define EINK_PARTIAL_REPEAT_LIMIT 5 - /* * eink display pins */ diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h index 5c0b529b0..f72c7661a 100644 --- a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -26,8 +26,8 @@ static const uint8_t LED_BUILTIN = 18; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 45; -static const uint8_t SCL = 46; +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/heltec_wireless_tracker_V1_0/platformio.ini b/variants/heltec_wireless_tracker_V1_0/platformio.ini index 034360c3d..303e27dbf 100644 --- a/variants/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/heltec_wireless_tracker_V1_0/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-wireless-tracker-V1-0] extends = esp32s3_base board = heltec_wireless_tracker -upload_protocol = esp-builtin +upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker_V1_0 diff --git a/variants/m5stack_coreink/platformio.ini b/variants/m5stack_coreink/platformio.ini index ee6d340dc..dfb078a0a 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/m5stack_coreink/platformio.ini @@ -9,8 +9,9 @@ build_flags = ;-D RADIOLIB_VERBOSE -Ofast -D__MCUXPRESSO - -DEPD_HEIGHT=200 - -DEPD_WIDTH=200 + -DEINK_DISPLAY_MODEL=GxEPD2_154_M09 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 -DUSER_SETUP_LOADED -DM5_COREINK -DM5STACK diff --git a/variants/my_esp32s3_diy_eink/platformio.ini b/variants/my_esp32s3_diy_eink/platformio.ini index d3c55afda..966bc580e 100644 --- a/variants/my_esp32s3_diy_eink/platformio.ini +++ b/variants/my_esp32s3_diy_eink/platformio.ini @@ -19,9 +19,9 @@ build_flags = ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink -Dmy - -DTECHO_DISPLAY_MODEL=GxEPD2_290_T5D - -DEPD_HEIGHT=128 - -DEPD_WIDTH=296 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DARDUINO_USB_MODE=0 diff --git a/variants/my_esp32s3_diy_eink/variant.h b/variants/my_esp32s3_diy_eink/variant.h index a5bebdacc..516fa7f34 100644 --- a/variants/my_esp32s3_diy_eink/variant.h +++ b/variants/my_esp32s3_diy_eink/variant.h @@ -53,4 +53,4 @@ #define PIN_EINK_DC 1 #define PIN_EINK_RES (-1) #define PIN_EINK_SCLK 5 -#define PIN_EINK_MOSI 6 +#define PIN_EINK_MOSI 6 \ No newline at end of file diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini index 736329eb8..37f785e84 100644 --- a/variants/rak10701/platformio.ini +++ b/variants/rak10701/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak10701 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak10701> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h index 5ff12a7de..837d081ff 100644 --- a/variants/rak10701/variant.h +++ b/variants/rak10701/variant.h @@ -202,13 +202,13 @@ static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b9789166f..b1bc2d9b5 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index cc18a901f..4ad99df44 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -181,13 +181,13 @@ static const uint8_t SCK = PIN_SPI_SCK; /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: @@ -219,6 +219,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define NRF_APM // enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings #define PIN_3V3_EN (34) // RAK1910 GPS module diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index 0871eee45..ced732d94 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -4,6 +4,9 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index 0a4f05344..c4a13ec82 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -6,6 +6,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -D PIN_EINK_EN=34 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/station-g2/pins_arduino.h b/variants/station-g2/pins_arduino.h new file mode 100755 index 000000000..98cbd46d3 --- /dev/null +++ b/variants/station-g2/pins_arduino.h @@ -0,0 +1,29 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) <= 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 + +// The default Wire will be mapped to Screen and Sensors +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 14; +static const uint8_t SCK = 12; +static const uint8_t MOSI = 13; +static const uint8_t SS = 11; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/station-g2/platformio.ini b/variants/station-g2/platformio.ini new file mode 100755 index 000000000..b39136684 --- /dev/null +++ b/variants/station-g2/platformio.ini @@ -0,0 +1,15 @@ +[env:station-g2] +extends = esp32s3_base +board = station-g2 +board_build.mcu = esp32s3 +upload_protocol = esptool +;upload_port = /dev/ttyACM0 +upload_speed = 921600 +lib_deps = + ${esp32s3_base.lib_deps} +build_unflags = -DARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} -D STATION_G2 -I variants/station-g2 + -DBOARD_HAS_PSRAM + -DSTATION_G2 + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h new file mode 100755 index 000000000..f781ceb24 --- /dev/null +++ b/variants/station-g2/variant.h @@ -0,0 +1,52 @@ +/* +Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 +*/ + +// Station G2 may not have GPS installed, but it has a GROVE GPS Socket for Optional GPS Module +#define GPS_RX_PIN 7 +#define GPS_TX_PIN 15 + +// Station G2 has 1.3 inch OLED Screen +#define USE_SH1107_128_64 + +#define I2C_SDA 5 // I2C pins for this board +#define I2C_SCL 6 + +#define BUTTON_PIN 38 // This is the Program Button +#define BUTTON_NEED_PULLUP + +#define USE_SX1262 + +#define LORA_MISO 14 +#define LORA_SCK 12 +#define LORA_MOSI 13 +#define LORA_CS 11 + +#define LORA_RESET 21 +#define LORA_DIO1 48 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 47 +#define SX126X_RESET LORA_RESET + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Ensure the PA does not exceed the saturation output power. More +// Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +#define SX126X_MAX_POWER 19 +#endif + +#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_MULTIPLIER 4 +#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. +#define BAT_FULLVOLT 8400 +#define BAT_EMPTYVOLT 5000 +#define BAT_CHARGINGVOLT 8400 +#define BAT_NOBATVOLT 4460 +#define CELL_TYPE_LION // same curve for liion/lipo +#define NUM_CELLS 2 diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 843bd88ff..49ba3bb34 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -8,6 +8,9 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DGPS_POWER_TOGGLE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 345091c2f..1af68863e 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -43,9 +43,10 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (0 + 14) // blue (confirmed on boards marked v1.0, date 2021-6-28) -#define PIN_LED2 (32 + 1) // green -#define PIN_LED3 (32 + 3) // red +#define PIN_LED1 (0 + 14) // 13 red (confirmed on 1.0 board) +// Unused(by firmware) LEDs: +#define PIN_LED2 (0 + 15) // 14 blue +#define PIN_LED3 (0 + 13) // 15 green #define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 diff --git a/version.properties b/version.properties index ed7e93154..14d1884fb 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 2 -build = 24 +build = 25