diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini new file mode 100644 index 000000000..ca4f576d6 --- /dev/null +++ b/arch/esp32/esp32s2.ini @@ -0,0 +1,47 @@ +[esp32s2_base] +extends = arduino_base +platform = platformio/espressif32@^5.2.0 +build_src_filter = + ${arduino_base.build_src_filter} - - - - - +upload_speed = 961200 +monitor_speed = 115200 +debug_init_break = tbreak setup +monitor_filters = esp32_exception_decoder +board_build.filesystem = littlefs + +# Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging. +# See library directory for BLE logging possible values: .pio/libdeps/tbeam/NimBLE-Arduino/src/log_common/log_common.h +# This overrides the BLE logging default of LOG_LEVEL_INFO (1) from: .pio/libdeps/tbeam/NimBLE-Arduino/src/esp_nimble_cfg.h +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DHAS_BLUETOOTH=0 + -DDEBUG_HEAP + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + caveman99/ESP32 Codec2@^1.0.1 + +lib_ignore = + segger_rtt + ESP32 BLE Arduino + +; customize the partition table +; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables +board_build.partitions = partition-table.csv + diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 82d858df8..ee66650fc 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -1,6 +1,6 @@ { "build": { - "arduino":{ + "arduino": { "ldscript": "esp32s3_out.ld" }, "core": "esp32", @@ -8,9 +8,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_DFU_ON_BOOT=1", - "-DARDUINO_USB_MSC_ON_BOOT=1", - "-DARDUINO_USB_MODE=1", + "-DARDUINO_USB_MODE=0", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/protobufs b/protobufs index 0a5959958..737d1fc01 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0a59599589bc77763e76302e533a4c4bfa5ec80e +Subproject commit 737d1fc01bd7f57e48e9b8cd53b780b314b09c5b diff --git a/src/ButtonThread.h b/src/ButtonThread.h index 0e9830f3f..f9131c84a 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -4,6 +4,7 @@ #include "concurrency/OSThread.h" #include "configuration.h" #include "graphics/Screen.h" +#include "modules/ExternalNotificationModule.h" #include "power.h" #include @@ -115,6 +116,10 @@ class ButtonThread : public concurrency::OSThread { // DEBUG_MSG("press!\n"); #ifdef BUTTON_PIN + // If a nag notification is running, stop it + if (externalNotificationModule->nagCycleCutoff != UINT32_MAX) { + externalNotificationModule->nagCycleCutoff = 0; + } if ((BUTTON_PIN != moduleConfig.canned_message.inputbroker_pin_press) || !moduleConfig.canned_message.enabled) { powerFSM.trigger(EVENT_PRESS); diff --git a/src/Power.cpp b/src/Power.cpp index 929f9a072..5b379f3c6 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -182,6 +182,9 @@ Power::Power() : OSThread("Power") { statusHandler = {}; low_voltage_counter = 0; +#ifdef DEBUG_HEAP + lastheap = ESP.getFreeHeap(); +#endif } bool Power::analogInit() @@ -284,7 +287,10 @@ void Power::readPowerStatus() powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP - DEBUG_MSG("Heap status: %d/%d bytes free, running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), concurrency::mainController.size(false)); + if (lastheap != ESP.getFreeHeap()){ + DEBUG_MSG("Heap status: %d/%d bytes free (%d), running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreeHeap() - lastheap , concurrency::mainController.size(false)); + lastheap = ESP.getFreeHeap(); + } #endif // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row @@ -458,6 +464,9 @@ bool Power::axpChipInit() // Set constant current charging current PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_450MA); + //Set up the charging voltage + PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); + } else if (PMU->getChipModel() == XPOWERS_AXP2101) { // t-beam s3 core @@ -510,6 +519,8 @@ bool Power::axpChipInit() //Set the constant current charging current of AXP2101, temporarily use 500mA by default PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); + //Set up the charging voltage + PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); } @@ -563,9 +574,6 @@ bool Power::axpChipInit() DEBUG_MSG("=======================================================================\n"); - //Set up the charging voltage, AXP2101/AXP192 4.2V gear is the same - // XPOWERS_AXP192_CHG_VOL_4V2 = XPOWERS_AXP2101_CHG_VOL_4V2 - PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); // Set PMU shutdown voltage at 2.6V to maximize battery utilization PMU->setSysPowerDownVoltage(2600); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 81ed10444..3ea4463ea 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -329,11 +329,8 @@ static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, i display->drawString(64 + x, y, "Updating"); display->setFont(FONT_SMALL); - if ((millis() / 1000) % 2) { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); - } else { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); - } + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL *2, x + display->getWidth(), "Please be patient and do not power off."); } /// Draw the last text message we received @@ -364,6 +361,9 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state { displayedNodeNum = 0; // Not currently showing a node pane + // the max length of this buffer is much longer than we can possibly print + static char tempBuf[237]; + MeshPacket &mp = devicestate.rx_text_message; NodeInfo *node = nodeDB.getNode(getFrom(&mp)); // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, @@ -373,16 +373,14 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // with the third parameter you can define the width after which words will // be wrapped. Currently only spaces and "-" are allowed for wrapping display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - String sender = (node && node->has_user) ? node->user.short_name : "???"; - display->drawString(0 + x, 0 + y, sender); display->setFont(FONT_SMALL); - - // the max length of this buffer is much longer than we can possibly print - static char tempBuf[96]; - snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.payload.bytes); - - display->drawStringMaxWidth(4 + x, 10 + y, SCREEN_WIDTH - (6 + x), tempBuf); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawStringf(0 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???"); + display->drawStringf(1 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???"); + display->setColor(WHITE); + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } /// Draw a series of fields in a column, wrapping to multiple colums if needed @@ -395,6 +393,10 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * int xo = x, yo = y; while (*f) { display->drawString(xo, yo, *f); + if (display->getColor() == BLACK) + display->drawString(xo + 1, yo, *f); + + display->setColor(WHITE); yo += FONT_HEIGHT_SMALL; if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) { xo += SCREEN_WIDTH / 2; @@ -465,6 +467,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); display->drawFastImage(x, y, 8, 8, imgUser); display->drawString(x + 10, y - 2, usersString); + display->drawString(x + 11, y - 2, usersString); } // Draw GPS status summary @@ -473,15 +476,18 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus if (config.position.fixed_position) { // GPS coordinates are currently fixed display->drawString(x - 1, y - 2, "Fixed GPS"); + display->drawString(x, y - 2, "Fixed GPS"); return; } if (!gps->getIsConnected()) { display->drawString(x, y - 2, "No GPS"); + display->drawString(x + 1, y - 2, "No GPS"); return; } display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); if (!gps->getHasLock()) { display->drawString(x + 8, y - 2, "No sats"); + display->drawString(x + 9, y - 2, "No sats"); return; } else { char satsString[3]; @@ -685,16 +691,16 @@ static uint16_t getCompassDiam(OLEDDisplay *display) { uint16_t diam = 0; // get the smaller of the 2 dimensions and subtract 20 - if(display->getWidth() > display->getHeight()) { - diam = display->getHeight(); + if(display->getWidth() > (display->getHeight() - FONT_HEIGHT_SMALL)) { + diam = display->getHeight() - FONT_HEIGHT_SMALL; // if 2/3 of the other size would be smaller, use that if (diam > (display->getWidth() * 2 / 3)) { diam = display->getWidth() * 2 / 3; } } else { diam = display->getWidth(); - if (diam > (display->getHeight() * 2 / 3)) { - diam = display->getHeight() * 2 / 3; + if (diam > ((display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3)) { + diam = (display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3; } } @@ -774,6 +780,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; @@ -804,7 +812,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; // coordinates for the center of the compass/circle - int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + SCREEN_HEIGHT / 2; + int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; bool hasNodeHeading = false; if (ourNode && hasPosition(ourNode)) { @@ -847,6 +855,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + display->setColor(BLACK); // Must be after distStr is populated drawColumns(display, x, y, fields); } @@ -1373,6 +1382,9 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + char channelStr[20]; { concurrency::LockGuard guard(&lock); @@ -1382,22 +1394,24 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Display power status if (powerStatus->getHasBattery()) - drawBattery(display, x, y + 2, imgBattery, powerStatus); + drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); else if (powerStatus->knowsUSB()) - display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); // Display nodes status - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); // Display GPS status if (!config.position.gps_enabled){ - int16_t yPos = y + 2; - #ifdef GPS_POWER_TOGGLE + int16_t yPos = y + 2; +#ifdef GPS_POWER_TOGGLE yPos = (y + 10 + FONT_HEIGHT_SMALL); - #endif - drawGPSpowerstat(display, x, yPos, gpsStatus); +#endif + drawGPSpowerstat(display, x, yPos, gpsStatus); } else { - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); } + display->setColor(WHITE); // Draw the channel name display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); // Draw our hardware ID to assist with bluetooth pairing @@ -1428,15 +1442,24 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + if (WiFi.status() != WL_CONNECTED) { display->drawString(x, y, String("WiFi: Not Connected")); + display->drawString(x + 1, y, String("WiFi: Not Connected")); } else { display->drawString(x, y, String("WiFi: Connected")); + display->drawString(x + 1, y, String("WiFi: Connected")); display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, "RSSI " + String(WiFi.RSSI())); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, + "RSSI " + String(WiFi.RSSI())); } + display->setColor(WHITE); + /* - WL_CONNECTED: assigned when connected to a WiFi network; - WL_NO_SSID_AVAIL: assigned when no SSID are available; @@ -1545,6 +1568,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + char batStr[20]; if (powerStatus->getHasBattery()) { int batV = powerStatus->getBatteryVoltageMv() / 1000; @@ -1555,9 +1581,11 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // Line 1 display->drawString(x, y, batStr); + display->drawString(x + 1, y, batStr); } else { // Line 1 display->drawString(x, y, String("USB")); + display->drawString(x + 1, y, String("USB")); } auto mode = ""; @@ -1590,6 +1618,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat } display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); // Line 2 uint32_t currentMillis = millis(); @@ -1602,6 +1631,8 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // minutes %= 60; // hours %= 24; + display->setColor(WHITE); + // Show uptime as days, hours, minutes OR seconds String uptime; if (days >= 2) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 23828b3ee..3988fa1a8 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -21,6 +21,7 @@ class Screen void startBluetoothPinScreen(uint32_t pin) {} void stopBluetoothPinScreen() {} void startRebootScreen() {} + void startFirmwareUpdateScreen() {} }; } diff --git a/src/graphics/images.h b/src/graphics/images.h index 41b390eaf..4680b9475 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -15,33 +15,3 @@ const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF const uint8_t imgInfo[] PROGMEM = { 0xFF, 0x81, 0x81, 0xB5, 0xB5, 0x81, 0x81, 0xFF }; #include "img/icon.xbm" - -// We now programmatically draw our compass -#if 0 -const -#include "img/compass.xbm" -#endif - -#if 0 -const uint8_t activeSymbol[] PROGMEM = { - B00000000, - B00000000, - B00011000, - B00100100, - B01000010, - B01000010, - B00100100, - B00011000 -}; - -const uint8_t inactiveSymbol[] PROGMEM = { - B00000000, - B00000000, - B00000000, - B00000000, - B00011000, - B00011000, - B00000000, - B00000000 -}; -#endif \ No newline at end of file diff --git a/src/graphics/img/compass.xbm b/src/graphics/img/compass.xbm deleted file mode 100644 index 115a7a1d4..000000000 --- a/src/graphics/img/compass.xbm +++ /dev/null @@ -1,28 +0,0 @@ -#define compass_width 48 -#define compass_height 48 -static char compass_bits[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, - 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, - 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00, - 0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, - 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01, - 0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01, - 0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, - 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03, - 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01, 0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01, - 0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, - 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00, - 0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, - 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00, - 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; diff --git a/src/graphics/img/pin.xbm b/src/graphics/img/pin.xbm deleted file mode 100644 index e8c8f8930..000000000 --- a/src/graphics/img/pin.xbm +++ /dev/null @@ -1,6 +0,0 @@ -#define pin_width 13 -#define pin_height 13 -static char pin_bits[] = { - 0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0xFC, 0x07, 0xBC, 0x07, 0xBC, 0x07, - 0xFC, 0x07, 0xF8, 0x03, 0xF8, 0x03, 0xF0, 0x01, 0xE0, 0x00, 0xE0, 0x00, - 0x00, 0x00, }; diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 818bacf45..925735903 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -41,6 +41,11 @@ void FloodingRouter::sniffReceived(const MeshPacket *p, const Routing *c) tosend->hop_limit--; // bump down the hop count + // If it is a traceRoute request, update the route that it went via me + if (p->which_payload_variant == MeshPacket_decoded_tag && traceRouteModule->wantPacket(p)) { + traceRouteModule->updateRoute(tosend); + } + printPacket("Rebroadcasting received floodmsg to neighbors", p); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 387b4576b..7e6271fc0 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -2,6 +2,7 @@ #include "PacketHistory.h" #include "Router.h" +#include "modules/TraceRouteModule.h" /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index c30542def..1e40b8d92 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -215,9 +215,9 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_external_notification = true; moduleConfig.has_canned_message = true; - strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(default_mqtt_address)); - strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(default_mqtt_username)); - strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(default_mqtt_password)); + strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address)); + strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(moduleConfig.mqtt.username)); + strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(moduleConfig.mqtt.password)); initModuleConfigIntervals(); } diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 567215600..18f2ef2f1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -3,6 +3,7 @@ #include "NodeDB.h" #include "SPILock.h" #include "configuration.h" +#include "main.h" #include "error.h" #include "mesh-pb-constants.h" #include @@ -87,10 +88,8 @@ bool RadioLibInterface::canSendImmediately() if (busyTx && (millis() - lastTxStart > 60000)) { DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n"); RECORD_CRITICALERROR(CriticalErrorCode_TRANSMIT_FAILED); -#ifdef ARCH_ESP32 - if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot - ESP.restart(); -#endif + // reboot in 5 seconds when this condition occurs. + rebootAtMsec = lastTxStart + 65000; } if (busyRx) DEBUG_MSG("Can not send yet, busyRx\n"); @@ -386,6 +385,7 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) int res = iface->startTransmit(radiobuf, numbytes); if (res != RADIOLIB_ERR_NONE) { + DEBUG_MSG("startTransmit failed, error=%d\n", res); RECORD_CRITICALERROR(CriticalErrorCode_RADIO_SPI_BUG); // This send failed, but make sure to 'complete' it properly diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index 596bb7b9a..c154a98db 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -151,7 +151,7 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define LocalConfig_size 387 -#define LocalModuleConfig_size 358 +#define LocalModuleConfig_size 376 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/module_config.pb.h b/src/mesh/generated/module_config.pb.h index 4b2e29f17..47f410a5c 100644 --- a/src/mesh/generated/module_config.pb.h +++ b/src/mesh/generated/module_config.pb.h @@ -93,6 +93,13 @@ typedef struct _ModuleConfig_ExternalNotificationConfig { bool alert_message; bool alert_bell; bool use_pwm; + uint8_t output_vibra; + uint8_t output_buzzer; + bool alert_message_vibra; + bool alert_message_buzzer; + bool alert_bell_vibra; + bool alert_bell_buzzer; + uint16_t nag_timeout; } ModuleConfig_ExternalNotificationConfig; typedef struct _ModuleConfig_MQTTConfig { @@ -187,7 +194,7 @@ extern "C" { #define ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0} #define ModuleConfig_AudioConfig_init_default {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0} @@ -196,7 +203,7 @@ extern "C" { #define ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0} #define ModuleConfig_AudioConfig_init_zero {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0} @@ -228,6 +235,13 @@ extern "C" { #define ModuleConfig_ExternalNotificationConfig_alert_message_tag 5 #define ModuleConfig_ExternalNotificationConfig_alert_bell_tag 6 #define ModuleConfig_ExternalNotificationConfig_use_pwm_tag 7 +#define ModuleConfig_ExternalNotificationConfig_output_vibra_tag 8 +#define ModuleConfig_ExternalNotificationConfig_output_buzzer_tag 9 +#define ModuleConfig_ExternalNotificationConfig_alert_message_vibra_tag 10 +#define ModuleConfig_ExternalNotificationConfig_alert_message_buzzer_tag 11 +#define ModuleConfig_ExternalNotificationConfig_alert_bell_vibra_tag 12 +#define ModuleConfig_ExternalNotificationConfig_alert_bell_buzzer_tag 13 +#define ModuleConfig_ExternalNotificationConfig_nag_timeout_tag 14 #define ModuleConfig_MQTTConfig_enabled_tag 1 #define ModuleConfig_MQTTConfig_address_tag 2 #define ModuleConfig_MQTTConfig_username_tag 3 @@ -323,7 +337,14 @@ X(a, STATIC, SINGULAR, UINT32, output, 3) \ X(a, STATIC, SINGULAR, BOOL, active, 4) \ X(a, STATIC, SINGULAR, BOOL, alert_message, 5) \ X(a, STATIC, SINGULAR, BOOL, alert_bell, 6) \ -X(a, STATIC, SINGULAR, BOOL, use_pwm, 7) +X(a, STATIC, SINGULAR, BOOL, use_pwm, 7) \ +X(a, STATIC, SINGULAR, UINT32, output_vibra, 8) \ +X(a, STATIC, SINGULAR, UINT32, output_buzzer, 9) \ +X(a, STATIC, SINGULAR, BOOL, alert_message_vibra, 10) \ +X(a, STATIC, SINGULAR, BOOL, alert_message_buzzer, 11) \ +X(a, STATIC, SINGULAR, BOOL, alert_bell_vibra, 12) \ +X(a, STATIC, SINGULAR, BOOL, alert_bell_buzzer, 13) \ +X(a, STATIC, SINGULAR, UINT32, nag_timeout, 14) #define ModuleConfig_ExternalNotificationConfig_CALLBACK NULL #define ModuleConfig_ExternalNotificationConfig_DEFAULT NULL @@ -391,7 +412,7 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg; /* Maximum encoded size of messages (where known) */ #define ModuleConfig_AudioConfig_size 19 #define ModuleConfig_CannedMessageConfig_size 49 -#define ModuleConfig_ExternalNotificationConfig_size 22 +#define ModuleConfig_ExternalNotificationConfig_size 40 #define ModuleConfig_MQTTConfig_size 169 #define ModuleConfig_RangeTestConfig_size 10 #define ModuleConfig_SerialConfig_size 26 diff --git a/src/mesh/generated/portnums.pb.h b/src/mesh/generated/portnums.pb.h index ec7b7eaec..74805ed10 100644 --- a/src/mesh/generated/portnums.pb.h +++ b/src/mesh/generated/portnums.pb.h @@ -82,6 +82,9 @@ typedef enum _PortNum { Maintained by GitHub user GUVWAF. Project files at https://github.com/GUVWAF/Meshtasticator */ PortNum_SIMULATOR_APP = 69, + /* Provides a traceroute functionality to show the route a packet towards + a certain destination would take on the mesh. */ + PortNum_TRACEROUTE_APP = 70, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/generated/telemetry.pb.h b/src/mesh/generated/telemetry.pb.h index 8c5c68bfa..16e69f442 100644 --- a/src/mesh/generated/telemetry.pb.h +++ b/src/mesh/generated/telemetry.pb.h @@ -69,10 +69,10 @@ typedef struct _EnvironmentMetrics { /* Types of Measurements the telemetry module is equipped to handle */ typedef struct _Telemetry { - /* This is usually not sent over the mesh (to save space), but it is sent - from the phone so that the local device can set its RTC If it is sent over - the mesh (because there are devices on the mesh without GPS), it will only - be sent by devices which has a hardware GPS clock (IE Mobile Phone). + /* This is usually not sent over the mesh (to save space), but it is sent + from the phone so that the local device can set its RTC If it is sent over + the mesh (because there are devices on the mesh without GPS), it will only + be sent by devices which has a hardware GPS clock (IE Mobile Phone). seconds since 1970 */ uint32_t time; pb_size_t which_variant; diff --git a/src/mesh/http/WiFiAPClient.cpp b/src/mesh/http/WiFiAPClient.cpp index bb4542468..731197e60 100644 --- a/src/mesh/http/WiFiAPClient.cpp +++ b/src/mesh/http/WiFiAPClient.cpp @@ -1,7 +1,7 @@ -#include "mesh/http/WiFiAPClient.h" #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" +#include "mesh/http/WiFiAPClient.h" #include "configuration.h" #include "main.h" #include "mesh/http/WebServer.h" @@ -37,9 +37,9 @@ bool APStartupComplete = 0; unsigned long lastrun_ntp = 0; -static bool needReconnect = true; // If we create our reconnector, run it once at the beginning +bool needReconnect = true; // If we create our reconnector, run it once at the beginning -static Periodic *wifiReconnect; +Periodic *wifiReconnect; static int32_t reconnectWiFi() { @@ -56,29 +56,15 @@ static int32_t reconnectWiFi() // Make sure we clear old connection credentials WiFi.disconnect(false, true); - DEBUG_MSG("... Reconnecting to WiFi access point %s\n",wifiName); - - int n = WiFi.scanNetworks(); - - if (n > 0) { - for (int i = 0; i < n; ++i) { - DEBUG_MSG("Found WiFi network %s, signal strength %d\n", WiFi.SSID(i).c_str(), WiFi.RSSI(i)); - yield(); - } - WiFi.mode(WIFI_MODE_STA); - WiFi.begin(wifiName, wifiPsw); - } else { - DEBUG_MSG("No networks found during site survey. Rebooting MCU...\n"); - screen->startRebootScreen(); - rebootAtMsec = millis() + 5000; - } - + DEBUG_MSG("Reconnecting to WiFi access point %s\n",wifiName); + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(wifiName, wifiPsw); } #ifndef DISABLE_NTP if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours - DEBUG_MSG("Updating NTP time\n"); + DEBUG_MSG("Updating NTP time from %s\n",config.network.ntp_server); if (timeClient.update()) { DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n"); @@ -129,7 +115,7 @@ static void onNetworkConnected() { if (!APStartupComplete) { // Start web server - DEBUG_MSG("... Starting network services\n"); + DEBUG_MSG("Starting network services\n"); // start mdns if (!MDNS.begin("Meshtastic")) { @@ -168,6 +154,8 @@ bool initWifi() createSSLCert(); + esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials + if (!*wifiPsw) // Treat empty password as no password wifiPsw = NULL; @@ -194,7 +182,7 @@ bool initWifi() WiFi.onEvent( [](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.print("\nWiFi lost connection. Reason: "); + Serial.print("WiFi lost connection. Reason: "); Serial.println(info.wifi_sta_disconnected.reason); /* @@ -221,91 +209,137 @@ bool initWifi() // Called by the Espressif SDK to static void WiFiEvent(WiFiEvent_t event) { - DEBUG_MSG("************ [WiFi-event] event: %d ************\n", event); + DEBUG_MSG("WiFi-Event %d: ", event); switch (event) { - case SYSTEM_EVENT_WIFI_READY: + case ARDUINO_EVENT_WIFI_READY: DEBUG_MSG("WiFi interface ready\n"); break; - case SYSTEM_EVENT_SCAN_DONE: + case ARDUINO_EVENT_WIFI_SCAN_DONE: DEBUG_MSG("Completed scan for access points\n"); break; - case SYSTEM_EVENT_STA_START: + case ARDUINO_EVENT_WIFI_STA_START: DEBUG_MSG("WiFi station started\n"); break; - case SYSTEM_EVENT_STA_STOP: + case ARDUINO_EVENT_WIFI_STA_STOP: DEBUG_MSG("WiFi station stopped\n"); break; - case SYSTEM_EVENT_STA_CONNECTED: + case ARDUINO_EVENT_WIFI_STA_CONNECTED: DEBUG_MSG("Connected to access point\n"); break; - case SYSTEM_EVENT_STA_DISCONNECTED: + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: DEBUG_MSG("Disconnected from WiFi access point\n"); WiFi.disconnect(false, true); needReconnect = true; wifiReconnect->setIntervalFromNow(1000); break; - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: DEBUG_MSG("Authentication mode of access point has changed\n"); break; - case SYSTEM_EVENT_STA_GOT_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP: DEBUG_MSG("Obtained IP address: "); Serial.println(WiFi.localIP()); onNetworkConnected(); break; - case SYSTEM_EVENT_STA_LOST_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + DEBUG_MSG("Obtained IP6 address: "); + Serial.println(WiFi.localIPv6()); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: DEBUG_MSG("Lost IP address and IP address is reset to 0\n"); WiFi.disconnect(false, true); needReconnect = true; wifiReconnect->setIntervalFromNow(1000); break; - case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: + case ARDUINO_EVENT_WPS_ER_SUCCESS: DEBUG_MSG("WiFi Protected Setup (WPS): succeeded in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_FAILED: + case ARDUINO_EVENT_WPS_ER_FAILED: DEBUG_MSG("WiFi Protected Setup (WPS): failed in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: + case ARDUINO_EVENT_WPS_ER_TIMEOUT: DEBUG_MSG("WiFi Protected Setup (WPS): timeout in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_PIN: + case ARDUINO_EVENT_WPS_ER_PIN: DEBUG_MSG("WiFi Protected Setup (WPS): pin code in enrollee mode\n"); break; - case SYSTEM_EVENT_AP_START: + case ARDUINO_EVENT_WPS_ER_PBC_OVERLAP: + DEBUG_MSG("WiFi Protected Setup (WPS): push button overlap in enrollee mode\n"); + break; + case ARDUINO_EVENT_WIFI_AP_START: DEBUG_MSG("WiFi access point started\n"); break; - case SYSTEM_EVENT_AP_STOP: + case ARDUINO_EVENT_WIFI_AP_STOP: DEBUG_MSG("WiFi access point stopped\n"); break; - case SYSTEM_EVENT_AP_STACONNECTED: + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: DEBUG_MSG("Client connected\n"); break; - case SYSTEM_EVENT_AP_STADISCONNECTED: + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: DEBUG_MSG("Client disconnected\n"); break; - case SYSTEM_EVENT_AP_STAIPASSIGNED: + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: DEBUG_MSG("Assigned IP address to client\n"); break; - case SYSTEM_EVENT_AP_PROBEREQRECVED: + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: DEBUG_MSG("Received probe request\n"); break; - case SYSTEM_EVENT_GOT_IP6: + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: DEBUG_MSG("IPv6 is preferred\n"); break; - case SYSTEM_EVENT_ETH_START: + case ARDUINO_EVENT_WIFI_FTM_REPORT: + DEBUG_MSG("Fast Transition Management report\n"); + break; + case ARDUINO_EVENT_ETH_START: DEBUG_MSG("Ethernet started\n"); break; - case SYSTEM_EVENT_ETH_STOP: + case ARDUINO_EVENT_ETH_STOP: DEBUG_MSG("Ethernet stopped\n"); break; - case SYSTEM_EVENT_ETH_CONNECTED: + case ARDUINO_EVENT_ETH_CONNECTED: DEBUG_MSG("Ethernet connected\n"); break; - case SYSTEM_EVENT_ETH_DISCONNECTED: + case ARDUINO_EVENT_ETH_DISCONNECTED: DEBUG_MSG("Ethernet disconnected\n"); break; - case SYSTEM_EVENT_ETH_GOT_IP: - DEBUG_MSG("Obtained IP address (SYSTEM_EVENT_ETH_GOT_IP)\n"); + case ARDUINO_EVENT_ETH_GOT_IP: + DEBUG_MSG("Obtained IP address (ARDUINO_EVENT_ETH_GOT_IP)\n"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + DEBUG_MSG("Obtained IP6 address (ARDUINO_EVENT_ETH_GOT_IP6)\n"); + break; + case ARDUINO_EVENT_SC_SCAN_DONE: + DEBUG_MSG("SmartConfig: Scan done\n"); + break; + case ARDUINO_EVENT_SC_FOUND_CHANNEL: + DEBUG_MSG("SmartConfig: Found channel\n"); + break; + case ARDUINO_EVENT_SC_GOT_SSID_PSWD: + DEBUG_MSG("SmartConfig: Got SSID and password\n"); + break; + case ARDUINO_EVENT_SC_SEND_ACK_DONE: + DEBUG_MSG("SmartConfig: Send ACK done\n"); + break; + case ARDUINO_EVENT_PROV_INIT: + DEBUG_MSG("Provisioning: Init\n"); + break; + case ARDUINO_EVENT_PROV_DEINIT: + DEBUG_MSG("Provisioning: Stopped\n"); + break; + case ARDUINO_EVENT_PROV_START: + DEBUG_MSG("Provisioning: Started\n"); + break; + case ARDUINO_EVENT_PROV_END: + DEBUG_MSG("Provisioning: End\n"); + break; + case ARDUINO_EVENT_PROV_CRED_RECV: + DEBUG_MSG("Provisioning: Credentials received\n"); + break; + case ARDUINO_EVENT_PROV_CRED_FAIL: + DEBUG_MSG("Provisioning: Credentials failed\n"); + break; + case ARDUINO_EVENT_PROV_CRED_SUCCESS: + DEBUG_MSG("Provisioning: Credentials success\n"); break; default: break; diff --git a/src/mesh/http/WiFiAPClient.h b/src/mesh/http/WiFiAPClient.h index 9729c24b5..a11330ad0 100644 --- a/src/mesh/http/WiFiAPClient.h +++ b/src/mesh/http/WiFiAPClient.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include "concurrency/Periodic.h" #include #include @@ -8,6 +9,9 @@ #include #endif +extern bool needReconnect; +extern concurrency::Periodic *wifiReconnect; + /// @return true if wifi is now in use bool initWifi(); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 434a42972..0b617adea 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -112,12 +112,15 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r) #ifdef ARCH_ESP32 if (BleOta::getOtaAppVersion().isEmpty()) { DEBUG_MSG("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); + screen->startRebootScreen(); }else{ + screen->startFirmwareUpdateScreen(); BleOta::switchToOtaApp(); DEBUG_MSG("Rebooting to OTA in %d seconds\n", s); } #else DEBUG_MSG("Not on ESP32, scheduling regular reboot in %d seconds\n", s); + screen->startRebootScreen(); #endif rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 5a95ceec1..b77f3a887 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -11,41 +11,9 @@ #define PIN_BUZZER false #endif -//#include - /* - Documentation: - https://github.com/meshtastic/firmware/blob/master/docs/software/modules/ExternalNotificationModule.md - - This module supports: - https://github.com/meshtastic/firmware/issues/654 - - - Quick reference: - - moduleConfig.external_notification.enabled - 0 = Disabled (Default) - 1 = Enabled - - moduleConfig.external_notification.active - 0 = Active Low (Default) - 1 = Active High - - moduleConfig.external_notification.alert_message - 0 = Disabled (Default) - 1 = Alert when a text message comes - - moduleConfig.external_notification.alert_bell - 0 = Disabled (Default) - 1 = Alert when the bell character is received - - moduleConfig.external_notification.output - GPIO of the output. (Default = 13) - - moduleConfig.external_notification.output_ms - Amount of time in ms for the alert. Default is 1000. - + https://meshtastic.org/docs/settings/moduleconfig/external-notification */ // Default configurations @@ -58,56 +26,97 @@ #define ASCII_BELL 0x07 -bool externalCurrentState = 0; -uint32_t externalTurnedOn = 0; +ExternalNotificationModule *externalNotificationModule; + +bool externalCurrentState[3] = {}; + +uint32_t externalTurnedOn[3] = {}; int32_t ExternalNotificationModule::runOnce() { - /* - Uncomment the preferences below if you want to use the module - without having to configure it from the PythonAPI or WebUI. - */ - - // moduleConfig.external_notification.enabled = 1; - // moduleConfig.external_notification.alert_message = 1; - - // moduleConfig.external_notification.active = 1; - // moduleConfig.external_notification.alert_bell = 1; - // moduleConfig.external_notification.output_ms = 1000; - // moduleConfig.external_notification.output = 13; - - if (externalCurrentState && !moduleConfig.external_notification.use_pwm) { + if (moduleConfig.external_notification.use_pwm || !moduleConfig.external_notification.enabled) { + return INT32_MAX; // we don't need this thread here... + } else { // If the output is turned on, turn it back off after the given period of time. - if (externalTurnedOn + (moduleConfig.external_notification.output_ms + if (nagCycleCutoff != UINT32_MAX) { + if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms - : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < - millis()) { - DEBUG_MSG("Turning off external notification\n"); - setExternalOff(); + : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) { + getExternal(0) ? setExternalOff(0) : setExternalOn(0); + } + if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms + ? moduleConfig.external_notification.output_ms + : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) { + getExternal(1) ? setExternalOff(1) : setExternalOn(1); + } + if (externalTurnedOn[2] + (moduleConfig.external_notification.output_ms + ? moduleConfig.external_notification.output_ms + : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) { + getExternal(2) ? setExternalOff(2) : setExternalOn(2); + } + } + + if (nagCycleCutoff < millis()) { + nagCycleCutoff = UINT32_MAX; + DEBUG_MSG("Turning off external notification: "); + for (int i = 0; i < 2; i++) { + if (getExternal(i)) { + setExternalOff(i); + externalTurnedOn[i] = 0; + DEBUG_MSG("%d ", i); + } + } + DEBUG_MSG("\n"); + return INT32_MAX; // save cycles till we're needed again } - } - if (moduleConfig.external_notification.use_pwm) - return INT32_MAX; // we don't need this thread here... - else return 25; + } } -void ExternalNotificationModule::setExternalOn() +void ExternalNotificationModule::setExternalOn(uint8_t index) { - externalCurrentState = 1; - externalTurnedOn = millis(); + externalCurrentState[index] = 1; + externalTurnedOn[index] = millis(); - digitalWrite(output, - (moduleConfig.external_notification.active ? true : false)); + switch(index) { + case 1: + if(moduleConfig.external_notification.output_vibra) + digitalWrite(moduleConfig.external_notification.output_vibra, true); + break; + case 2: + if(moduleConfig.external_notification.output_buzzer) + digitalWrite(moduleConfig.external_notification.output_buzzer, true); + break; + default: + digitalWrite(output, (moduleConfig.external_notification.active ? true : false)); + break; + } } -void ExternalNotificationModule::setExternalOff() +void ExternalNotificationModule::setExternalOff(uint8_t index) { - externalCurrentState = 0; + externalCurrentState[index] = 0; + externalTurnedOn[index] = millis(); - digitalWrite(output, - (moduleConfig.external_notification.active ? false : true)); + switch(index) { + case 1: + if(moduleConfig.external_notification.output_vibra) + digitalWrite(moduleConfig.external_notification.output_vibra, false); + break; + case 2: + if(moduleConfig.external_notification.output_buzzer) + digitalWrite(moduleConfig.external_notification.output_buzzer, false); + break; + default: + digitalWrite(output, (moduleConfig.external_notification.active ? false : true)); + break; + } +} + +bool ExternalNotificationModule::getExternal(uint8_t index) +{ + return externalCurrentState[index]; } // -------- @@ -121,13 +130,18 @@ ExternalNotificationModule::ExternalNotificationModule() without having to configure it from the PythonAPI or WebUI. */ - // moduleConfig.external_notification.enabled = 1; - // moduleConfig.external_notification.alert_message = 1; + // moduleConfig.external_notification.enabled = true; + // moduleConfig.external_notification.alert_message = true; + // moduleConfig.external_notification.alert_message_buzzer = true; + // moduleConfig.external_notification.alert_message_vibra = true; - // moduleConfig.external_notification.active = 1; + // moduleConfig.external_notification.active = true; // moduleConfig.external_notification.alert_bell = 1; // moduleConfig.external_notification.output_ms = 1000; - // moduleConfig.external_notification.output = 13; + // moduleConfig.external_notification.output = 4; // RAK4631 IO4 + // moduleConfig.external_notification.output_buzzer = 10; // RAK4631 IO6 + // moduleConfig.external_notification.output_vibra = 28; // RAK4631 IO7 + // moduleConfig.external_notification.nag_timeout = 300; if (moduleConfig.external_notification.enabled) { @@ -141,8 +155,20 @@ ExternalNotificationModule::ExternalNotificationModule() // Set the direction of a pin DEBUG_MSG("Using Pin %i in digital mode\n", output); pinMode(output, OUTPUT); - // Turn off the pin - setExternalOff(); + setExternalOff(0); + externalTurnedOn[0] = 0; + if(moduleConfig.external_notification.output_vibra) { + DEBUG_MSG("Using Pin %i for vibra motor\n", moduleConfig.external_notification.output_vibra); + pinMode(moduleConfig.external_notification.output_vibra, OUTPUT); + setExternalOff(1); + externalTurnedOn[1] = 0; + } + if(moduleConfig.external_notification.output_buzzer) { + DEBUG_MSG("Using Pin %i for buzzer\n", moduleConfig.external_notification.output_buzzer); + pinMode(moduleConfig.external_notification.output_buzzer, OUTPUT); + setExternalOff(2); + externalTurnedOn[2] = 0; + } } else { config.device.buzzer_gpio = config.device.buzzer_gpio ? config.device.buzzer_gpio @@ -163,17 +189,53 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp) if (getFrom(&mp) != nodeDB.getNodeNum()) { - // TODO: This may be a problem if messages are sent in unicide, but I'm not sure if it will. - // Need to know if and how this could be a problem. + // Check if the message contains a bell character. Don't do this loop for every pin, just once. + auto &p = mp.decoded; + bool containsBell = false; + for (int i = 0; i < p.payload.size; i++) { + if (p.payload.bytes[i] == ASCII_BELL) { + containsBell = true; + } + } + if (moduleConfig.external_notification.alert_bell) { - auto &p = mp.decoded; - DEBUG_MSG("externalNotificationModule - Notification Bell\n"); - for (int i = 0; i < p.payload.size; i++) { - if (p.payload.bytes[i] == ASCII_BELL) { - if (!moduleConfig.external_notification.use_pwm) { - setExternalOn(); + if (containsBell) { + DEBUG_MSG("externalNotificationModule - Notification Bell\n"); + if (!moduleConfig.external_notification.use_pwm) { + setExternalOn(0); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; } else { - playBeep(); + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; + } + // run_once now + } else { + playBeep(); + } + } + } + + if (!moduleConfig.external_notification.use_pwm) { + if (moduleConfig.external_notification.alert_bell_vibra) { + if (containsBell) { + DEBUG_MSG("externalNotificationModule - Notification Bell (Vibra)\n"); + setExternalOn(1); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } else { + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; + } + } + } + + if (moduleConfig.external_notification.alert_bell_buzzer) { + if (containsBell) { + DEBUG_MSG("externalNotificationModule - Notification Bell (Buzzer)\n"); + setExternalOn(2); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } else { + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; } } } @@ -182,11 +244,39 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp) if (moduleConfig.external_notification.alert_message) { DEBUG_MSG("externalNotificationModule - Notification Module\n"); if (!moduleConfig.external_notification.use_pwm) { - setExternalOn(); + setExternalOn(0); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } else { + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; + } } else { playBeep(); } } + + if (!moduleConfig.external_notification.use_pwm) { + if (moduleConfig.external_notification.alert_message_vibra) { + DEBUG_MSG("externalNotificationModule - Notification Module (Vibra)\n"); + setExternalOn(1); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } else { + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; + } + } + + if (moduleConfig.external_notification.alert_message_buzzer) { + DEBUG_MSG("externalNotificationModule - Notification Module (Buzzer)\n"); + setExternalOn(2); + if (moduleConfig.external_notification.nag_timeout) { + nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } else { + nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; + } + } + } + setIntervalFromNow(0); // run once so we know if we should do something } } else { diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index b5ab64b3c..1b1aeff21 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -17,18 +17,19 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: public: ExternalNotificationModule(); - void setExternalOn(); - void setExternalOff(); - void getExternal(); + uint32_t nagCycleCutoff = UINT32_MAX; + + void setExternalOn(uint8_t index = 0); + void setExternalOff(uint8_t index = 0); + bool getExternal(uint8_t index = 0); protected: - // virtual MeshPacket *allocReply(); - /** Called to handle a particular incoming message - @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ virtual ProcessMessage handleReceived(const MeshPacket &mp) override; virtual int32_t runOnce() override; }; + +extern ExternalNotificationModule *externalNotificationModule; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index b0b923863..9978f2487 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -11,6 +11,7 @@ #include "modules/ReplyModule.h" #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" +#include "modules/TraceRouteModule.h" #include "modules/WaypointModule.h" #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" @@ -40,6 +41,7 @@ void setupModules() positionModule = new PositionModule(); waypointModule = new WaypointModule(); textMessageModule = new TextMessageModule(); + traceRouteModule = new TraceRouteModule(); // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. @@ -67,7 +69,7 @@ void setupModules() #ifdef ARCH_ESP32 // Only run on an esp32 based device. audioModule = new AudioModule(); - new ExternalNotificationModule(); + externalNotificationModule = new ExternalNotificationModule(); storeForwardModule = new StoreForwardModule(); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 05ed44cd7..d25e196a1 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -82,7 +82,7 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") int32_t SerialModule::runOnce() { -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2) /* Uncomment the preferences below if you want to use the module without having to configure it from the PythonAPI or WebUI. @@ -214,7 +214,6 @@ int32_t SerialModule::runOnce() MeshPacket *SerialModuleRadio::allocReply() { - auto reply = allocDataPacket(); // Allocate a packet for sending return reply; @@ -236,7 +235,7 @@ void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies) ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp) { -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2) if (moduleConfig.serial.enabled) { auto &p = mp.decoded; @@ -266,7 +265,12 @@ ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp) if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_DEFAULT || moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) { Serial2.printf("%s", p.payload.bytes); - + } else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) { + NodeInfo *node = nodeDB.getNode(getFrom(&mp)); + String sender = (node && node->has_user) ? node->user.short_name : "???"; + Serial2.println(); + Serial2.printf("%s: %s", sender, p.payload.bytes); + Serial2.println(); } else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) { // TODO this needs to be implemented } else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_NMEA) { diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp new file mode 100644 index 000000000..1c5fd97d3 --- /dev/null +++ b/src/modules/TraceRouteModule.cpp @@ -0,0 +1,86 @@ +#include "TraceRouteModule.h" +#include "MeshService.h" +#include "FloodingRouter.h" + +TraceRouteModule *traceRouteModule; + + +bool TraceRouteModule::handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r) +{ + // Only handle a response + if (mp.decoded.request_id) { + printRoute(r, mp.to, mp.from); + } + + return false; // let it be handled by RoutingModule +} + + +void TraceRouteModule::updateRoute(MeshPacket* p) +{ + auto &incoming = p->decoded; + // Only append an ID for the request (one way) + if (!incoming.request_id) { + RouteDiscovery scratch; + RouteDiscovery *updated = NULL; + memset(&scratch, 0, sizeof(scratch)); + pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, RouteDiscovery_fields, &scratch); + updated = &scratch; + + appendMyID(updated); + printRoute(updated, p->from, NODENUM_BROADCAST); + + // Set updated route to the payload of the to be flooded packet + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), RouteDiscovery_fields, updated); + } +} + + +void TraceRouteModule::appendMyID(RouteDiscovery* updated) +{ + // Length of route array can normally not be exceeded due to the max. hop_limit of 7 + if (updated->route_count < sizeof(updated->route)/sizeof(updated->route[0])) { + updated->route[updated->route_count] = myNodeInfo.my_node_num; + updated->route_count += 1; + } else { + DEBUG_MSG("WARNING: Route exceeded maximum hop limit, are you bridging networks?\n"); + } +} + + +void TraceRouteModule::printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest) +{ + DEBUG_MSG("Route traced:\n"); + DEBUG_MSG("0x%x --> ", origin); + for (uint8_t i=0; iroute_count; i++) { + DEBUG_MSG("0x%x --> ", r->route[i]); + } + if (dest != NODENUM_BROADCAST) DEBUG_MSG("0x%x\n", dest); else DEBUG_MSG("...\n"); +} + + +MeshPacket* TraceRouteModule::allocReply() +{ + assert(currentRequest); + + // Copy the payload of the current request + auto req = *currentRequest; + auto &p = req.decoded; + RouteDiscovery scratch; + RouteDiscovery *updated = NULL; + memset(&scratch, 0, sizeof(scratch)); + pb_decode_from_bytes(p.payload.bytes, p.payload.size, RouteDiscovery_fields, &scratch); + updated = &scratch; + + printRoute(updated, req.from, req.to); + + // Create a MeshPacket with this payload and set it as the reply + MeshPacket* reply = allocDataProtobuf(*updated); + + return reply; +} + + +TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", PortNum_TRACEROUTE_APP, RouteDiscovery_fields) { + ourPortNum = PortNum_TRACEROUTE_APP; +} diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h new file mode 100644 index 000000000..15b7a564d --- /dev/null +++ b/src/modules/TraceRouteModule.h @@ -0,0 +1,36 @@ +#pragma once +#include "ProtobufModule.h" + + +/** + * A module that traces the route to a certain destination node + */ +class TraceRouteModule : public ProtobufModule +{ + public: + TraceRouteModule(); + + // Let FloodingRouter call updateRoute upon rebroadcasting a TraceRoute request + friend class FloodingRouter; + + protected: + bool handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r) override; + + virtual MeshPacket *allocReply() override; + + /* Call before rebroadcasting a RouteDiscovery payload in order to update + the route array containing the IDs of nodes this packet went through */ + void updateRoute(MeshPacket* p); + + private: + // Call to add your ID to the route array of a RouteDiscovery message + void appendMyID(RouteDiscovery *r); + + /* Call to print the route array of a RouteDiscovery message. + Set origin to where the request came from. + Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ + void printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest); + +}; + +extern TraceRouteModule *traceRouteModule; \ No newline at end of file diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 4e784e1a9..e7132fcc4 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -10,6 +10,10 @@ #include +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.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. @@ -41,48 +45,72 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -//int16_t 1KHz sine test tone -int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 }; -int Sine1KHz_index = 0; +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) +// 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 +#define FONT_SMALL ArialMT_Plain_10 +#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) void run_codec2(void* parameter) { - // 4 bytes of header in each frame Kennung hex c0 de c2 plus the bitrate + // 4 bytes of header in each frame hex c0 de c2 plus the bitrate memcpy(audioModule->tx_encode_frame,&audioModule->tx_header,sizeof(audioModule->tx_header)); + DEBUG_MSG("Starting codec2 task\n"); + while (true) { uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000)); if (tcount != 0) { if (audioModule->radio_state == RadioState::tx) { - - // Apply the TX filter for (int i = 0; i < audioModule->adc_buffer_size; i++) audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]); - // Encode the audio codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech); - - //increment the pointer where the encoded frame must be saved audioModule->tx_encode_frame_index += audioModule->encode_codec_size; - //If it this is reached we have a ready trasnmission frame if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header))) { - //Transmit it - DEBUG_MSG("♪♫♪ Sending %d codec2 bytes\n", audioModule->encode_frame_size); + DEBUG_MSG("Sending %d codec2 bytes\n", audioModule->encode_frame_size); audioModule->sendPayload(); audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header); } } if (audioModule->radio_state == RadioState::rx) { - //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) - { - //Decode the codec2 frame - codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); - size_t bytesOut = 0; - i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + size_t bytesOut = 0; + if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) { + for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) + { + codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); + i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + } + } else { + // if the buffer header does not match our own codec, make a temp decoding setup. + CODEC2* tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]); + codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2); + int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8; + int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2); + for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) + { + codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); + i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + } + codec2_destroy(tmp_codec2); } } } @@ -91,8 +119,15 @@ void run_codec2(void* parameter) AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { + // moduleConfig.audio.codec2_enabled = true; + // moduleConfig.audio.i2s_ws = 13; + // moduleConfig.audio.i2s_sd = 15; + // moduleConfig.audio.i2s_din = 22; + // moduleConfig.audio.i2s_sck = 14; + // moduleConfig.audio.ptt_pin = 39; + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + DEBUG_MSG("Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); memcpy(tx_header.magic,c2_magic,sizeof(c2_magic)); tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1; @@ -104,7 +139,31 @@ AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask); } else { - DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); + DEBUG_MSG("Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); + } +} + +void AudioModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + displayedNodeNum = 0; // Not currently showing a node pane + + char buffer[50]; + + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawStringf(0 + x, 0 + y, buffer, "Codec2 Mode %d Audio", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + display->setColor(WHITE); + display->setFont(FONT_LARGE); + display->setTextAlignment(TEXT_ALIGN_CENTER); + switch (radio_state) { + case RadioState::tx: + display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "PTT"); + break; + default: + display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "Receive"); + break; } } @@ -114,7 +173,7 @@ int32_t AudioModule::runOnce() esp_err_t res; if (firstTime) { // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC - DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); + DEBUG_MSG("Initializing I2S SD: %d DIN: %d WS: %d SCK: %d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), .sample_rate = 8000, @@ -130,7 +189,7 @@ int32_t AudioModule::runOnce() }; res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); + DEBUG_MSG("Failed to install I2S driver: %d\n", res); const i2s_pin_config_t pin_config = { .bck_io_num = moduleConfig.audio.i2s_sck, @@ -140,36 +199,41 @@ int32_t AudioModule::runOnce() }; res = i2s_set_pin(I2S_PORT, &pin_config); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); + DEBUG_MSG("Failed to set I2S pin config: %d\n", res); res = i2s_start(I2S_PORT); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); + DEBUG_MSG("Failed to start I2S: %d\n", res); radio_state = RadioState::rx; // Configure PTT input - DEBUG_MSG("♪♫♪ Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); firstTime = false; } else { + UIFrameEvent e = {false, true}; // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { - DEBUG_MSG("♪♫♪ PTT pressed, switching to TX\n"); + DEBUG_MSG("PTT pressed, switching to TX\n"); radio_state = RadioState::tx; + e.frameChanged = true; + this->notifyObservers(&e); } } else { if (radio_state == RadioState::tx) { + DEBUG_MSG("PTT released, switching to RX\n"); if (tx_encode_frame_index > sizeof(tx_header)) { // Send the incomplete frame - DEBUG_MSG("♪♫♪ Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); + DEBUG_MSG("Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); sendPayload(); } - DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; + e.frameChanged = true; + this->notifyObservers(&e); } } if (radio_state == RadioState::tx) { @@ -194,7 +258,7 @@ int32_t AudioModule::runOnce() } return 100; } else { - DEBUG_MSG("♪♫♪ Audio Module Disabled\n"); + DEBUG_MSG("Audio Module Disabled\n"); return INT32_MAX; } @@ -202,17 +266,25 @@ int32_t AudioModule::runOnce() MeshPacket *AudioModule::allocReply() { - auto reply = allocDataPacket(); // Allocate a packet for sending + auto reply = allocDataPacket(); return reply; } +bool AudioModule::shouldDraw() +{ + if (!moduleConfig.audio.codec2_enabled) { + return false; + } + return (radio_state == RadioState::tx); +} + void AudioModule::sendPayload(NodeNum dest, bool wantReplies) { MeshPacket *p = allocReply(); p->to = dest; p->decoded.want_response = wantReplies; - p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? + p->want_ack = false; // Audio is shoot&forget. No need to wait for ACKs. p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime p->decoded.payload.size = tx_encode_frame_index; diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index 8e813ad1e..1a41ab6c4 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include enum RadioState { standby, rx, tx }; @@ -28,7 +30,7 @@ struct c2_header { #define AUDIO_MODULE_RX_BUFFER 128 #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 -class AudioModule : public SinglePortModule, private concurrency::OSThread +class AudioModule : public SinglePortModule, public Observable, private concurrency::OSThread { public: unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; @@ -50,6 +52,8 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread AudioModule(); + bool shouldDraw(); + /** * Send our payload into the mesh */ @@ -63,6 +67,15 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread virtual MeshPacket *allocReply() override; + virtual bool wantUIFrame() override { return this->shouldDraw(); } + virtual Observable* getUIFrameObservable() override { return this; } +#if !HAS_SCREEN + void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#else + virtual void drawFrame( + OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif + /** Called to handle a particular incoming message * @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index e51296824..9d1c44c18 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -16,52 +16,50 @@ StoreForwardModule *storeForwardModule; int32_t StoreForwardModule::runOnce() { - #ifdef ARCH_ESP32 + if (moduleConfig.store_forward.enabled && is_server) { + // Send out the message queue. + if (this->busy) { + // Only send packets if the channel is less than 25% utilized. + if (airTime->channelUtilizationPercent() < polite_channel_util_percent) { - if (moduleConfig.store_forward.enabled) { + // DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index); + storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - if (config.device.role == Config_DeviceConfig_Role_ROUTER) { - - // Send out the message queue. - if (this->busy) { - - // Only send packets if the channel is less than 25% utilized. - if (airTime->channelUtilizationPercent() < polite_channel_util_percent) { - - // DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index); - storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - - if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) { - strcpy(this->routerMessage, "** S&F - Done"); - storeForwardModule->sendMessage(this->busyTo, this->routerMessage); - - // DEBUG_MSG("--- --- --- In busy loop - Done \n"); - this->packetHistoryTXQueue_index = 0; - this->busy = false; - } else { - this->packetHistoryTXQueue_index++; - } + if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) { + strcpy(this->routerMessage, "** S&F - Done"); + storeForwardModule->sendMessage(this->busyTo, this->routerMessage); + // DEBUG_MSG("--- --- --- In busy loop - Done \n"); + this->packetHistoryTXQueue_index = 0; + this->busy = false; } else { - DEBUG_MSG("Channel utilization is too high. Skipping this opportunity to send and will retry later.\n"); + this->packetHistoryTXQueue_index++; } + + } else { + DEBUG_MSG("Channel utilization is too high. Retrying later.\n"); } - DEBUG_MSG("SF myNodeInfo.bitrate = %f bytes / sec\n", myNodeInfo.bitrate); + DEBUG_MSG("SF bitrate = %f bytes / sec\n", myNodeInfo.bitrate); - return (this->packetTimeMax); - } else { - DEBUG_MSG("Store & Forward Module - Disabled (is_router = false)\n"); + } else if (millis() - lastHeartbeat > 300000) { + lastHeartbeat = millis(); + DEBUG_MSG("Sending heartbeat\n"); + + StoreAndForward sf; + sf.rr = StoreAndForward_RequestResponse_ROUTER_HEARTBEAT; + sf.has_heartbeat = true; + sf.heartbeat.period = 300; + sf.heartbeat.secondary = 0; // TODO we always have one primary router for now - return (INT32_MAX); + MeshPacket *p = allocDataProtobuf(sf); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = MeshPacket_Priority_MIN; + service.sendToMesh(p); } - - } else { - DEBUG_MSG("Store & Forward Module - Disabled\n"); - - return (INT32_MAX); + return (this->packetTimeMax); } - #endif return (INT32_MAX); } @@ -76,12 +74,7 @@ void StoreForwardModule::populatePSRAM() https://learn.upesy.com/en/programmation/psram.html#psram-tab */ - DEBUG_MSG("Before PSRAM initilization:\n"); - - DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize()); - DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap()); - DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize()); - DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram()); + DEBUG_MSG("Before PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize()); this->packetHistoryTXQueue = static_cast(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct))); @@ -93,19 +86,12 @@ void StoreForwardModule::populatePSRAM() this->packetHistory = static_cast(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))); - DEBUG_MSG("After PSRAM initilization:\n"); - - DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize()); - DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap()); - DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize()); - DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram()); - DEBUG_MSG("Store and Forward Stats:\n"); - DEBUG_MSG(" numberOfPackets for packetHistory - %u\n", numberOfPackets); + DEBUG_MSG("After PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize()); + DEBUG_MSG("numberOfPackets for packetHistory - %u\n", numberOfPackets); } void StoreForwardModule::historyReport() { - DEBUG_MSG("Iterating through the message history...\n"); DEBUG_MSG("Message history contains %u records\n", this->packetHistoryCurrent); } @@ -246,8 +232,8 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) DEBUG_MSG("--- S&F Received something\n"); - // The router node should not be sending messages as a client. - if (getFrom(&mp) != nodeDB.getNodeNum()) { + // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT + if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) { DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n"); @@ -264,6 +250,7 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) } else { storeForwardModule->historySend(1000 * 60, getFrom(&mp)); } + } else if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 'm') && (p.payload.bytes[3] == 0x00)) { strlcpy(this->routerMessage, @@ -278,14 +265,12 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) } } else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) { + DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); } else { DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum); } } - - } else { - DEBUG_MSG("Store & Forward Module - Disabled\n"); } #endif @@ -293,92 +278,107 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) return ProcessMessage::CONTINUE; // Let others look at this message also if they want } -ProcessMessage StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p) +bool StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p) { if (!moduleConfig.store_forward.enabled) { // If this module is not enabled in any capacity, don't handle the packet, and allow other modules to consume - return ProcessMessage::CONTINUE; + return false; } - if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) { - DEBUG_MSG("Packet came from an PortNum_TEXT_MESSAGE_APP port %u\n", mp.decoded.portnum); - return ProcessMessage::CONTINUE; - } else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) { - DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); - + if (mp.decoded.portnum != PortNum_STORE_FORWARD_APP) { + DEBUG_MSG("Packet came from port %u\n", mp.decoded.portnum); + return false; } else { - DEBUG_MSG("Packet came from an UNKNOWN port %u\n", mp.decoded.portnum); - return ProcessMessage::CONTINUE; - } + DEBUG_MSG("Packet came from PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); + switch (p->rr) { - case StoreAndForward_RequestResponse_CLIENT_ERROR: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n"); - break; + case StoreAndForward_RequestResponse_CLIENT_ERROR: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n"); + } + break; - case StoreAndForward_RequestResponse_CLIENT_HISTORY: - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n"); + case StoreAndForward_RequestResponse_CLIENT_HISTORY: + if(is_server) { + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n"); + // Send the last 60 minutes of messages. + if (this->busy) { + strcpy(this->routerMessage, "** S&F - Busy. Try again shortly."); + storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage); + } else { + storeForwardModule->historySend(1000 * 60, getFrom(&mp)); + } + } + break; - // Send the last 60 minutes of messages. - if (this->busy) { - strcpy(this->routerMessage, "** S&F - Busy. Try again shortly."); - storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage); - } else { - storeForwardModule->historySend(1000 * 60, getFrom(&mp)); + case StoreAndForward_RequestResponse_CLIENT_PING: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n"); + } + break; + + case StoreAndForward_RequestResponse_CLIENT_PONG: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n"); + } + break; + + case StoreAndForward_RequestResponse_CLIENT_STATS: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_BUSY: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_ERROR: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_PING: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_PONG: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n"); + } + break; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot } - - break; - - case StoreAndForward_RequestResponse_CLIENT_PING: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n"); - break; - - case StoreAndForward_RequestResponse_CLIENT_PONG: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n"); - break; - - case StoreAndForward_RequestResponse_CLIENT_STATS: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_BUSY: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_ERROR: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_PING: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_PONG: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n"); - break; - - default: - assert(0); // unexpected state - FIXME, make an error code and reboot } - return ProcessMessage::STOP; // There's no need for others to look at this message. + return true; // There's no need for others to look at this message. } StoreForwardModule::StoreForwardModule() - : SinglePortModule("StoreForwardModule", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("StoreForwardModule") + : concurrency::OSThread("StoreForwardModule"), ProtobufModule("StoreForward", PortNum_STORE_FORWARD_APP, &StoreAndForward_msg) { #ifdef ARCH_ESP32 @@ -397,9 +397,9 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.enabled) { // Router - if (config.device.role == Config_DeviceConfig_Role_ROUTER) { - DEBUG_MSG("Initializing Store & Forward Module - Enabled as Router\n"); - if (ESP.getPsramSize()) { + if ((config.device.role == Config_DeviceConfig_Role_ROUTER) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { + DEBUG_MSG("Initializing Store & Forward Module in Router mode\n"); + if (ESP.getPsramSize() > 0) { if (ESP.getFreePsram() >= 1024 * 1024) { // Do the startup here @@ -416,26 +416,27 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.records) this->records = moduleConfig.store_forward.records; - // Maximum number of records to store in memory + // send heartbeat advertising? if (moduleConfig.store_forward.heartbeat) this->heartbeat = moduleConfig.store_forward.heartbeat; // Popupate PSRAM with our data structures. this->populatePSRAM(); - + is_server = true; } else { - DEBUG_MSG("Device has less than 1M of PSRAM free. Aborting startup.\n"); - DEBUG_MSG("Store & Forward Module - Aborting Startup.\n"); + DEBUG_MSG("Device has less than 1M of PSRAM free.\n"); + DEBUG_MSG("Store & Forward Module - disabling server.\n"); } - } else { DEBUG_MSG("Device doesn't have PSRAM.\n"); - DEBUG_MSG("Store & Forward Module - Aborting Startup.\n"); + DEBUG_MSG("Store & Forward Module - disabling server.\n"); } // Client - } else { - DEBUG_MSG("Initializing Store & Forward Module - Enabled as Client\n"); + } + if ((config.device.role == Config_DeviceConfig_Role_CLIENT) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { + is_client = true; + DEBUG_MSG("Initializing Store & Forward Module in Client mode\n"); } } #endif diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index 2c6d6ec6e..32d3cddc9 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -1,6 +1,6 @@ #pragma once -#include "SinglePortModule.h" +#include "ProtobufModule.h" #include "concurrency/OSThread.h" #include "mesh/generated/storeforward.pb.h" @@ -18,9 +18,8 @@ struct PacketHistoryStruct { pb_size_t payload_size; }; -class StoreForwardModule : public SinglePortModule, private concurrency::OSThread +class StoreForwardModule : private concurrency::OSThread, public ProtobufModule { - // bool firstTime = 1; bool busy = 0; uint32_t busyTo = 0; char routerMessage[Constants_DATA_PAYLOAD_LEN] = {0}; @@ -34,7 +33,12 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea uint32_t packetHistoryTXQueue_size = 0; uint32_t packetHistoryTXQueue_index = 0; - uint32_t packetTimeMax = 2000; + uint32_t packetTimeMax = 5000; + + unsigned long lastHeartbeat = 0; + + bool is_client = false; + bool is_server = false; public: StoreForwardModule(); @@ -78,7 +82,7 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea it */ virtual ProcessMessage handleReceived(const MeshPacket &mp) override; - virtual ProcessMessage handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p); + virtual bool handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p); }; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 8af009bad..03549f911 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -7,6 +7,7 @@ #include "mesh/Router.h" #include "mesh/generated/mqtt.pb.h" #include "mesh/generated/telemetry.pb.h" +#include "mesh/http/WiFiAPClient.h" #include "sleep.h" #if HAS_WIFI #include @@ -20,6 +21,10 @@ String statusTopic = "msh/2/stat/"; String cryptTopic = "msh/2/c/"; // msh/2/c/CHANNELID/NODEID String jsonTopic = "msh/2/json/"; // msh/2/json/CHANNELID/NODEID +static MemoryDynamic staticMqttPool; + +Allocator &mqttPool = staticMqttPool; + void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) { mqtt->onPublish(topic, payload, length); @@ -61,7 +66,27 @@ void MQTT::onPublish(char *topic, byte *payload, unsigned int length) } else { DEBUG_MSG("JSON Ignoring downlink message we originally sent.\n"); } - } else { + } else if ((json.find("sender") != json.end()) && (json.find("payload") != json.end()) && (json.find("type") != json.end()) && json["type"]->IsString() && (json["type"]->AsString().compare("sendposition") == 0)) { + //invent the "sendposition" type for a valid envelope + if (json["payload"]->IsObject() && json["type"]->IsString() && (json["sender"]->AsString().compare(owner.id) != 0)) { + JSONObject posit; + posit=json["payload"]->AsObject(); //get nested JSON Position + Position pos =Position_init_default; + pos.latitude_i=posit["latitude_i"]->AsNumber(); + pos.longitude_i=posit["longitude_i"]->AsNumber(); + pos.altitude=posit["altitude"]->AsNumber(); + pos.time=posit["time"]->AsNumber(); + + // construct protobuf data packet using POSITION, send it to the mesh + MeshPacket *p = router->allocForSending(); + p->decoded.portnum = PortNum_POSITION_APP; + p->decoded.payload.size=pb_encode_to_bytes(p->decoded.payload.bytes,sizeof(p->decoded.payload.bytes),Position_fields, &pos); //make the Data protobuf from position + service.sendToMesh(p, RX_SRC_LOCAL); + + } else { + DEBUG_MSG("JSON Ignoring downlink message we originally sent.\n"); + } + } else{ DEBUG_MSG("JSON Received payload on MQTT but not a valid envelope\n"); } } else { @@ -101,7 +126,7 @@ void mqttInit() new MQTT(); } -MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient) +MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient), mqttQueue(MAX_MQTT_QUEUE) { assert(!mqtt); mqtt = this; @@ -148,14 +173,23 @@ void MQTT::reconnect() DEBUG_MSG("MQTT connected\n"); enabled = true; // Start running background process again runASAP = true; + reconnectCount = 0; /// FIXME, include more information in the status text bool ok = pubSub.publish(myStatus.c_str(), "online", true); DEBUG_MSG("published %d\n", ok); sendSubscriptions(); - } else - DEBUG_MSG("Failed to contact MQTT server...\n"); + } else { + DEBUG_MSG("Failed to contact MQTT server (%d/10)...\n",reconnectCount); +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + if (reconnectCount > 9) { + needReconnect = true; + wifiReconnect->setIntervalFromNow(1000); + } +#endif + reconnectCount++; + } } } @@ -211,8 +245,35 @@ int32_t MQTT::runOnce() if (wantConnection) { reconnect(); - // If we succeeded, start reading rapidly, else try again in 30 seconds (TCP connections are EXPENSIVE so try rarely) - return pubSub.connected() ? 20 : 30000; + // If we succeeded, empty the queue one by one and start reading rapidly, else try again in 30 seconds (TCP connections are EXPENSIVE so try rarely) + if (pubSub.connected()) { + if (!mqttQueue.isEmpty()) { + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + ServiceEnvelope *env = mqttQueue.dequeuePtr(0); + static uint8_t bytes[MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), ServiceEnvelope_fields, env); + + String topic = cryptTopic + env->channel_id + "/" + owner.id; + DEBUG_MSG("publish %s, %u bytes from queue\n", topic.c_str(), numBytes); + + + pubSub.publish(topic.c_str(), bytes, numBytes, false); + + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = this->downstreamPacketToJson(env->packet); + if (jsonString.length() != 0) { + String topicJson = jsonTopic + env->channel_id + "/" + owner.id; + DEBUG_MSG("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); + pubSub.publish(topicJson.c_str(), jsonString.c_str(), false); + } + } + mqttPool.release(env); + } + return 20; + } else { + return 30000; + } } else return 5000; // If we don't want connection now, check again in 5 secs } else { @@ -231,33 +292,48 @@ void MQTT::onSend(const MeshPacket &mp, ChannelIndex chIndex) { auto &ch = channels.getByIndex(chIndex); - // don't bother sending if not connected... - if (pubSub.connected() && ch.settings.uplink_enabled) { + if (ch.settings.uplink_enabled) { const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel - ServiceEnvelope env = ServiceEnvelope_init_default; - env.channel_id = (char *)channelId; - env.gateway_id = owner.id; - env.packet = (MeshPacket *)∓ + ServiceEnvelope *env = mqttPool.allocZeroed(); + env->channel_id = (char *)channelId; + env->gateway_id = owner.id; + env->packet = (MeshPacket *)∓ - // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets - static uint8_t bytes[MeshPacket_size + 64]; - size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), ServiceEnvelope_fields, &env); + // don't bother sending if not connected... + if (pubSub.connected()) { - String topic = cryptTopic + channelId + "/" + owner.id; - DEBUG_MSG("publish %s, %u bytes\n", topic.c_str(), numBytes); + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), ServiceEnvelope_fields, env); - pubSub.publish(topic.c_str(), bytes, numBytes, false); + String topic = cryptTopic + channelId + "/" + owner.id; + DEBUG_MSG("publish %s, %u bytes\n", topic.c_str(), numBytes); - if (moduleConfig.mqtt.json_enabled) { - // handle json topic - auto jsonString = this->downstreamPacketToJson((MeshPacket *)&mp); - if (jsonString.length() != 0) { - String topicJson = jsonTopic + channelId + "/" + owner.id; - DEBUG_MSG("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); - pubSub.publish(topicJson.c_str(), jsonString.c_str(), false); + pubSub.publish(topic.c_str(), bytes, numBytes, false); + + if (moduleConfig.mqtt.json_enabled) { + // handle json topic + auto jsonString = this->downstreamPacketToJson((MeshPacket *)&mp); + if (jsonString.length() != 0) { + String topicJson = jsonTopic + channelId + "/" + owner.id; + DEBUG_MSG("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), jsonString.c_str()); + pubSub.publish(topicJson.c_str(), jsonString.c_str(), false); + } } + } else { + DEBUG_MSG("MQTT not connected, queueing packet\n"); + if (mqttQueue.numFree() == 0) { + DEBUG_MSG("NOTE: MQTT queue is full, discarding oldest\n"); + ServiceEnvelope *d = mqttQueue.dequeuePtr(0); + if (d) + mqttPool.release(d); + } + // make a copy of serviceEnvelope and queue it + ServiceEnvelope *copied = mqttPool.allocCopy(*env); + assert(mqttQueue.enqueue(copied, 0)); } + mqttPool.release(env); } } diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index c8381574c..ddbacbcc4 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -4,6 +4,7 @@ #include "concurrency/OSThread.h" #include "mesh/Channels.h" +#include "mesh/generated/mqtt.pb.h" #include #if HAS_WIFI #include @@ -12,6 +13,8 @@ #include #endif +#define MAX_MQTT_QUEUE 32 + /** * Our wrapper/singleton for sending/receiving MQTT "udp" packets. This object isolates the MQTT protocol implementation from * the two components that use it: MQTTPlugin and MQTTSimInterface. @@ -52,6 +55,10 @@ class MQTT : private concurrency::OSThread bool connected(); protected: + PointerQueue mqttQueue; + + int reconnectCount = 0; + virtual int32_t runOnce() override; private: diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 21df018d8..e36a8cfd8 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -3,7 +3,9 @@ #include "esp_task_wdt.h" #include "main.h" +#if !defined(CONFIG_IDF_TARGET_ESP32S2) #include "nimble/NimbleBluetooth.h" +#endif #include "BleOta.h" #include "mesh/http/WiFiAPClient.h" @@ -16,13 +18,9 @@ #include #include "soc/rtc.h" +#if !defined(CONFIG_IDF_TARGET_ESP32S2) NimbleBluetooth *nimbleBluetooth; -void getMacAddr(uint8_t *dmac) -{ - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); -} - void setBluetoothEnable(bool on) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { @@ -36,6 +34,15 @@ void setBluetoothEnable(bool on) { } } } +#else +void setBluetoothEnable(bool on) { } +void updateBatteryLevel(uint8_t level) { } +#endif + +void getMacAddr(uint8_t *dmac) +{ + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +} #ifdef HAS_32768HZ #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) diff --git a/src/power.h b/src/power.h index 64043eb86..b370e0248 100644 --- a/src/power.h +++ b/src/power.h @@ -40,6 +40,9 @@ class Power : private concurrency::OSThread private: uint8_t low_voltage_counter; +#ifdef DEBUG_HEAP + uint32_t lastheap; +#endif }; extern Power *power; diff --git a/src/sleep.cpp b/src/sleep.cpp index 72b7f7527..40426a777 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -332,6 +332,8 @@ void enableModemSleep() #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32S2 + esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; #else esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; #endif diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 51da8590f..d811bad20 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -169,6 +169,7 @@ static const uint8_t SCK = PIN_SPI_SCK; IO4 <-> P0.04 (Arduino GPIO number 4) IO5 <-> P0.09 (Arduino GPIO number 9) IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) SW1 <-> P0.01 (Arduino GPIO number 1) A0 <-> P0.04/AIN2 (Arduino Analog A2 A1 <-> P0.31/AIN7 (Arduino Analog A7 diff --git a/variants/rak4631_epaper/variant.h b/variants/rak4631_epaper/variant.h index cf1b8b07d..71ce58714 100644 --- a/variants/rak4631_epaper/variant.h +++ b/variants/rak4631_epaper/variant.h @@ -169,6 +169,7 @@ static const uint8_t SCK = PIN_SPI_SCK; IO4 <-> P0.04 (Arduino GPIO number 4) IO5 <-> P0.09 (Arduino GPIO number 9) IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) SW1 <-> P0.01 (Arduino GPIO number 1) A0 <-> P0.04/AIN2 (Arduino Analog A2 A1 <-> P0.31/AIN7 (Arduino Analog A7