diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 9f1de95c5c..8b28090ca5 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -66,6 +66,7 @@ jobs: - board: tlora-v2-1-1_6 - board: tlora-v2-1-1_8 - board: tbeam + - board: heltec-ht62-esp32c3-sx1262 - board: heltec-v1 - board: heltec-v2_0 - board: heltec-v2_1 @@ -212,6 +213,9 @@ jobs: repository: ${{github.event.pull_request.head.repo.full_name}} gather-artifacts: + permissions: + contents: write + pull-requests: write runs-on: ubuntu-latest needs: [ @@ -285,14 +289,13 @@ jobs: - name: Create request artifacts continue-on-error: true # FIXME: Why are we getting 502, but things still work? if: ${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request' }} - uses: gavv/pull-request-artifacts@v1.1.0 + uses: gavv/pull-request-artifacts@v2.1.0 with: commit: ${{ (github.event.pull_request_target || github.event.pull_request).head.sha }} repo-token: ${{ secrets.GITHUB_TOKEN }} artifacts-token: ${{ secrets.ARTIFACTS_TOKEN }} artifacts-repo: meshtastic/artifacts artifacts-branch: device - artifacts-dir: pr artifacts: ./firmware-${{ steps.version.outputs.version }}.zip release-artifacts: diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 5933850e79..e739d70661 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#05255283879a0c65a7d3eba6c468b9186438bb14 +platform = https://github.com/meshtastic/platform-native.git#ff5da1d203b5c1163cfcda858d5f84920187f030 framework = arduino build_src_filter = @@ -28,5 +28,4 @@ build_flags = ${arduino_base.build_flags} -fPIC -Isrc/platform/portduino - -DRADIOLIB_EEPROM_UNSUPPORTED - + -DRADIOLIB_EEPROM_UNSUPPORTED \ No newline at end of file diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index cde45d1f87..266a9ae203 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -1,5 +1,5 @@ -# Define your devices here using Broadcom pin numbering -# Uncomment the block that corresponds to your hardware +### Define your devices here using Broadcom pin numbering +### Uncomment the block that corresponds to your hardware --- Lora: # Module: sx1262 # Waveshare SX126X XXXM @@ -25,16 +25,40 @@ Lora: # CS: 7 # IRQ: 25 -# Set gpio chip to use in /dev/. Defaults to 0. -# Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 +### Set gpio chip to use in /dev/. Defaults to 0. +### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 # gpiochip: 4 -# Define GPIO buttons here: +### Define GPIO buttons here: GPIO: # User: 6 -# Define GPS +### Define GPS GPS: # SerialPath: /dev/ttyS0 + +### Set up SPI displays here. Note that I2C displays are generally auto-detected. + +Display: + +### Waveshare 2.8inch RPi LCD +# Panel: ST7789 +# CS: 8 +# DC: 22 # Data/Command pin +# Backlight: 18 +# Width: 240 +# Height: 320 +# Reset: 27 +# Rotate: true + +Touchscreen: +# Module: XPT2046 +# CS: 7 +# IRQ: 17 + +### Configure device for direct keyboard input + +Input: +# KeyboardDevice: /dev/input/event0 diff --git a/bin/meshtasticd.service b/bin/meshtasticd.service index 4ed1bfd8fa..f15fdc8714 100644 --- a/bin/meshtasticd.service +++ b/bin/meshtasticd.service @@ -1,9 +1,12 @@ -[unit] -description=Meshtastic Native Daemon +[Unit] +Description=Meshtastic Native Daemon +After=network-online.target [Service] +User=root +Group=root Type=simple ExecStart=/usr/sbin/meshtasticd [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/AudioThread.h b/src/AudioThread.h new file mode 100644 index 0000000000..c9f253440f --- /dev/null +++ b/src/AudioThread.h @@ -0,0 +1,77 @@ +#pragma once +#include "PowerFSM.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#include "main.h" +#include "sleep.h" + +#ifdef HAS_I2S +#include +#include +#include +#include + +#define AUDIO_THREAD_INTERVAL_MS 100 + +class AudioThread : public concurrency::OSThread +{ + public: + AudioThread() : OSThread("AudioThread") { initOutput(); } + + void beginRttl(const void *data, uint32_t len) + { + setCPUFast(true); + rtttlFile = new AudioFileSourcePROGMEM(data, len); + i2sRtttl = new AudioGeneratorRTTTL(); + i2sRtttl->begin(rtttlFile, audioOut); + } + + bool isPlaying() + { + if (i2sRtttl != nullptr) { + return i2sRtttl->isRunning() && i2sRtttl->loop(); + } + return false; + } + + void stop() + { + if (i2sRtttl != nullptr) { + i2sRtttl->stop(); + delete i2sRtttl; + i2sRtttl = nullptr; + } + if (rtttlFile != nullptr) { + delete rtttlFile; + rtttlFile = nullptr; + } + + setCPUFast(false); + } + + protected: + int32_t runOnce() override + { + canSleep = true; // Assume we should not keep the board awake + + // if (i2sRtttl != nullptr && i2sRtttl->isRunning()) { + // i2sRtttl->loop(); + // } + return AUDIO_THREAD_INTERVAL_MS; + } + + private: + void initOutput() + { + audioOut = new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S); + audioOut->SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT); + audioOut->SetGain(0.2); + }; + + AudioGeneratorRTTTL *i2sRtttl = nullptr; + AudioOutputI2S *audioOut; + + AudioFileSourcePROGMEM *rtttlFile; +}; + +#endif diff --git a/src/ButtonThread.h b/src/ButtonThread.h index a60b7730a9..5f68aa5b68 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -5,6 +5,7 @@ #include "configuration.h" #include "graphics/Screen.h" #include "main.h" +#include "modules/ExternalNotificationModule.h" #include "power.h" #include @@ -205,6 +206,12 @@ class ButtonThread : public concurrency::OSThread static void userButtonPressedLongStart() { +#ifdef T_DECK + // False positive long-press triggered on T-Deck with i2s audio, so short circuit + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { + return; + } +#endif if (millis() > 30 * 1000) { LOG_DEBUG("Long press start!\n"); longPressTime = millis(); diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h index 541522f436..b757f3abb8 100644 --- a/src/PowerFSMThread.h +++ b/src/PowerFSMThread.h @@ -33,7 +33,7 @@ class PowerFSMThread : public OSThread powerFSM.trigger(EVENT_SHUTDOWN); } - return 10; + return 100; } }; diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index a9701d3605..d0893e9979 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -312,10 +312,22 @@ bool GPS::setup() _serial_gps->write("$PCAS11,3*1E\r\n"); delay(250); } else if (gnssModel == GNSS_MODEL_UC6580) { - + // The Unicore UC6580 can use a lot of sat systems, enable it to // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS + // This will reset the receiver, so wait a bit afterwards + // The paranoid will wait for the OK*04 confirmation response after each command. _serial_gps->write("$CFGSYS,h25155\r\n"); + delay(750); + // Must be done after the CFGSYS command + // Turn off GSV messages, we don't really care about which and where the sats are, maybe someday. + _serial_gps->write("$CFGMSG,0,3,0\r\n"); + delay(250); + // Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care. + _serial_gps->write("$CFGMSG,6,0,0\r\n"); delay(250); + _serial_gps->write("$CFGMSG,6,1,0\r\n"); + delay(250); + } else if (gnssModel == GNSS_MODEL_UBLOX) { // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command) // We need set it because by default it is GPS only, and we want to use GLONASS too diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 417a6e4544..a7fcd0c349 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -52,6 +52,10 @@ along with this program. If not, see . #include "modules/esp32/StoreForwardModule.h" #endif +#if ARCH_RASPBERRY_PI +#include "platform/portduino/PortduinoGlue.h" +#endif + #ifdef OLED_RU #include "fonts/OLEDDisplayFontsRU.h" #endif @@ -909,10 +913,40 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ } Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry) - : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32), - dispdev(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE), - ui(&dispdev) + : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { +#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) + dispdev = new SH1106Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_SSD1306) + dispdev = new SSD1306Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_EINK) + dispdev = new EInkDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_ST7567) + dispdev = new ST7567Wire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif ARCH_RASPBERRY_PI + if (settingsMap[displayPanel] == st7789) { + LOG_DEBUG("Making TFTDisplay!\n"); + dispdev = new TFTDisplay(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + } else { + dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + isAUTOOled = true; + } +#else + dispdev = new AutoOLEDWire(address.address, -1, -1, geometry, + (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); + isAUTOOled = true; +#endif + + ui = new OLEDDisplayUi(dispdev); cmdQueue.setReader(this); } @@ -925,8 +959,8 @@ void Screen::doDeepSleep() #ifdef USE_EINK static FrameCallback sleepFrames[] = {drawSleepScreen}; static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]); - ui.setFrames(sleepFrames, sleepFrameCount); - ui.update(); + ui->setFrames(sleepFrames, sleepFrameCount); + ui->update(); #endif setOn(false); } @@ -942,14 +976,16 @@ void Screen::handleSetOn(bool on) #ifdef T_WATCH_S3 PMU->enablePowerOutput(XPOWERS_ALDO2); #endif - dispdev.displayOn(); - dispdev.displayOn(); +#if !ARCH_RASPBERRY_PI + dispdev->displayOn(); +#endif + dispdev->displayOn(); enabled = true; setInterval(0); // Draw ASAP runASAP = true; } else { LOG_INFO("Turning off screen\n"); - dispdev.displayOff(); + dispdev->displayOff(); #ifdef T_WATCH_S3 PMU->disablePowerOutput(XPOWERS_ALDO2); #endif @@ -966,32 +1002,33 @@ void Screen::setup() useDisplay = true; #ifdef AutoOLEDWire_h - dispdev.setDetected(model); + if (isAUTOOled) + static_cast(dispdev)->setDetected(model); #endif #ifdef USE_SH1107_128_64 - dispdev.setSubtype(7); + static_cast(dispdev)->setSubtype(7); #endif // Initialising the UI will init the display too. - ui.init(); + ui->init(); - displayWidth = dispdev.width(); - displayHeight = dispdev.height(); + displayWidth = dispdev->width(); + displayHeight = dispdev->height(); - ui.setTimePerTransition(0); + ui->setTimePerTransition(0); - ui.setIndicatorPosition(BOTTOM); + ui->setIndicatorPosition(BOTTOM); // Defines where the first frame is located in the bar. - ui.setIndicatorDirection(LEFT_RIGHT); - ui.setFrameAnimation(SLIDE_LEFT); + ui->setIndicatorDirection(LEFT_RIGHT); + ui->setFrameAnimation(SLIDE_LEFT); // Don't show the page swipe dots while in boot screen. - ui.disableAllIndicators(); + ui->disableAllIndicators(); // Store a pointer to Screen so we can get to it from static functions. - ui.getUiState()->userData = this; + ui->getUiState()->userData = this; // Set the utf8 conversion function - dispdev.setFontTableLookupFunction(customFontTableLookup); + dispdev->setFontTableLookupFunction(customFontTableLookup); if (strlen(oemStore.oem_text) > 0) logo_timeout *= 2; @@ -999,23 +1036,23 @@ void Screen::setup() // Add frames. static FrameCallback bootFrames[] = {drawBootScreen}; static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); - ui.setFrames(bootFrames, bootFrameCount); + ui->setFrames(bootFrames, bootFrameCount); // No overlays. - ui.setOverlays(nullptr, 0); + ui->setOverlays(nullptr, 0); // Require presses to switch between frames. - ui.disableAutoTransition(); + ui->disableAutoTransition(); // Set up a log buffer with 3 lines, 32 chars each. - dispdev.setLogBuffer(3, 32); + dispdev->setLogBuffer(3, 32); #ifdef SCREEN_MIRROR - dispdev.mirrorScreen(); + dispdev->mirrorScreen(); #else // Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically // flip it. If you have a headache now, you're welcome. if (!config.display.flip_screen) { - dispdev.flipScreenVertically(); + dispdev->flipScreenVertically(); } #endif @@ -1023,20 +1060,30 @@ void Screen::setup() uint8_t dmac[6]; getMacAddr(dmac); snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); +#if ARCH_RASPBERRY_PI + handleSetOn(false); // force clean init +#endif // Turn on the display. handleSetOn(true); // On some ssd1306 clones, the first draw command is discarded, so draw it // twice initially. Skip this for EINK Displays to save a few seconds during boot - ui.update(); + ui->update(); #ifndef USE_EINK - ui.update(); + ui->update(); #endif serialSinceMsec = millis(); -#if HAS_TOUCHSCREEN - touchScreenImpl1 = new TouchScreenImpl1(dispdev.getWidth(), dispdev.getHeight(), dispdev.getTouch); +#if ARCH_RASPBERRY_PI + if (settingsMap[touchscreenModule]) { + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); + touchScreenImpl1->init(); + } +#elif HAS_TOUCHSCREEN + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); #endif @@ -1057,7 +1104,7 @@ void Screen::forceDisplay() { // Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup. #ifdef USE_EINK - dispdev.forceDisplay(); + static_cast(dispdev)->forceDisplay(); #endif } @@ -1088,10 +1135,10 @@ int32_t Screen::runOnce() // Change frames. static FrameCallback bootOEMFrames[] = {drawOEMBootScreen}; static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]); - ui.setFrames(bootOEMFrames, bootOEMFrameCount); - ui.update(); + ui->setFrames(bootOEMFrames, bootOEMFrameCount); + ui->update(); #ifndef USE_EINK - ui.update(); + ui->update(); #endif showingOEMBootScreen = false; } @@ -1164,16 +1211,16 @@ int32_t Screen::runOnce() // this must be before the frameState == FIXED check, because we always // want to draw at least one FIXED frame before doing forceDisplay - ui.update(); + ui->update(); // Switch to a low framerate (to save CPU) when we are not in transition // but we should only call setTargetFPS when framestate changes, because // otherwise that breaks animations. - if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED) { - // oldFrameState = ui.getUiState()->frameState; + if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) { + // oldFrameState = ui->getUiState()->frameState; targetFramerate = IDLE_FRAMERATE; - ui.setTargetFPS(targetFramerate); + ui->setTargetFPS(targetFramerate); forceDisplay(); } @@ -1189,7 +1236,7 @@ int32_t Screen::runOnce() } // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate, - // ui.getUiState()->frameState); If we are scrolling we need to be called + // ui->getUiState()->frameState); If we are scrolling we need to be called // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice // as fast as we really need so that any rounding errors still result with // the correct framerate @@ -1221,8 +1268,8 @@ void Screen::setSSLFrames() if (address_found.address) { // LOG_DEBUG("showing SSL frames\n"); static FrameCallback sslFrames[] = {drawSSLScreen}; - ui.setFrames(sslFrames, 1); - ui.update(); + ui->setFrames(sslFrames, 1); + ui->update(); } } @@ -1306,8 +1353,8 @@ void Screen::setFrames() LOG_DEBUG("Finished building frames. numframes: %d\n", numframes); - ui.setFrames(normalFrames, numframes); - ui.enableAllIndicators(); + ui->setFrames(normalFrames, numframes); + ui->enableAllIndicators(); prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list // just changed) @@ -1327,8 +1374,8 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) { - ui.disableAllIndicators(); - ui.setFrames(drawFrames, 1); + ui->disableAllIndicators(); + ui->setFrames(drawFrames, 1); setFastFramerate(); } @@ -1370,17 +1417,17 @@ void Screen::blink() { setFastFramerate(); uint8_t count = 10; - dispdev.setBrightness(254); + dispdev->setBrightness(254); while (count > 0) { - dispdev.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - dispdev.display(); + dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + dispdev->display(); delay(50); - dispdev.clear(); - dispdev.display(); + dispdev->clear(); + dispdev->display(); delay(50); count = count - 1; } - dispdev.setBrightness(brightness); + dispdev->setBrightness(brightness); } std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds) @@ -1408,15 +1455,15 @@ void Screen::handlePrint(const char *text) if (!useDisplay || !showingNormalScreen) return; - dispdev.print(text); + dispdev->print(text); } void Screen::handleOnPress() { // If screen was off, just wake it, otherwise advance to next frame // If we are in a transition, the press must have bounced, drop it. - if (ui.getUiState()->frameState == FIXED) { - ui.nextFrame(); + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); lastScreenTransition = millis(); setFastFramerate(); } @@ -1426,8 +1473,8 @@ void Screen::handleShowPrevFrame() { // If screen was off, just wake it, otherwise go back to previous frame // If we are in a transition, the press must have bounced, drop it. - if (ui.getUiState()->frameState == FIXED) { - ui.previousFrame(); + if (ui->getUiState()->frameState == FIXED) { + ui->previousFrame(); lastScreenTransition = millis(); setFastFramerate(); } @@ -1437,8 +1484,8 @@ void Screen::handleShowNextFrame() { // If screen was off, just wake it, otherwise advance to next frame // If we are in a transition, the press must have bounced, drop it. - if (ui.getUiState()->frameState == FIXED) { - ui.nextFrame(); + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); lastScreenTransition = millis(); setFastFramerate(); } @@ -1453,7 +1500,7 @@ void Screen::setFastFramerate() // We are about to start a transition so speed up fps targetFramerate = SCREEN_TRANSITION_FRAMERATE; - ui.setTargetFPS(targetFramerate); + ui->setTargetFPS(targetFramerate); setInterval(0); // redraw ASAP runASAP = true; } @@ -1540,7 +1587,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } #endif } else { -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ + // TODO: Raspberry Pi supports more than just the one screen size +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_RASPBERRY_PI) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); @@ -1780,7 +1828,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) setFastFramerate(); // TODO: We might also want switch to corresponding frame, // but we don't know the exact frame number. - // ui.switchToFrame(0); + // ui->switchToFrame(0); } } diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 554fa0aeb6..baee4b1400 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -324,6 +324,8 @@ class Screen : public concurrency::OSThread // Called periodically from the main loop. int32_t runOnce() final; + bool isAUTOOled = false; + private: struct ScreenCmd { Cmd cmd; @@ -385,22 +387,10 @@ class Screen : public concurrency::OSThread DebugInfo debugInfo; /// Display device + OLEDDisplay *dispdev; -#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) - SH1106Wire dispdev; -#elif defined(USE_SSD1306) - SSD1306Wire dispdev; -#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) - TFTDisplay dispdev; -#elif defined(USE_EINK) - EInkDisplay dispdev; -#elif defined(USE_ST7567) - ST7567Wire dispdev; -#else - AutoOLEDWire dispdev; -#endif /// UI helper for rendering to frames and switching between them - OLEDDisplayUi ui; + OLEDDisplayUi *ui; }; } // namespace graphics diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 618880a5cb..fe98882b48 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,5 +1,8 @@ #include "configuration.h" #include "main.h" +#if ARCH_RASPBERRY_PI +#include "platform/portduino/PortduinoGlue.h" +#endif #ifndef TFT_BACKLIGHT_ON #define TFT_BACKLIGHT_ON HIGH @@ -103,11 +106,11 @@ class LGFX : public lgfx::LGFX_Device } }; -static LGFX tft; +static LGFX *tft = nullptr; #elif defined(RAK14014) #include -TFT_eSPI tft = TFT_eSPI(); +TFT_eSPI *tft = nullptr; #elif defined(ST7789_CS) #include // Graphics and font library for ST7735 driver chip @@ -233,7 +236,7 @@ class LGFX : public lgfx::LGFX_Device } }; -static LGFX tft; +static LGFX *tft = nullptr; #elif defined(ILI9341_DRIVER) @@ -322,23 +325,96 @@ class LGFX : public lgfx::LGFX_Device } }; -static LGFX tft; +static LGFX *tft = nullptr; #elif defined(ST7735_CS) #include // Graphics and font library for ILI9341 driver chip -static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h +static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h +#elif ARCH_RASPBERRY_PI +#include // Graphics and font library for ST7735 driver chip + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_LCD *_panel_instance; + lgfx::Bus_SPI _bus_instance; + + lgfx::ITouch *_touch_instance; + + public: + LGFX(void) + { + + _panel_instance = new lgfx::Panel_ST7789; + auto buscfg = _bus_instance.config(); + buscfg.spi_mode = 0; + + buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) + + _bus_instance.config(buscfg); // applies the set value to the bus. + _panel_instance->setBus(&_bus_instance); // set the bus on the panel. + + auto cfg = _panel_instance->config(); // Gets a structure for display panel settings. + LOG_DEBUG("Height: %d, Width: %d \n", settingsMap[displayHeight], settingsMap[displayWidth]); + cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable) + cfg.panel_width = settingsMap[displayWidth]; // actual displayable width + cfg.panel_height = settingsMap[displayHeight]; // actual displayable height + cfg.offset_x = 0; // Panel offset amount in X direction + cfg.offset_y = 0; // Panel offset amount in Y direction + cfg.offset_rotation = 0; // Rotation direction value offset 0~7 (4~7 is mirrored) + cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout + cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read + cfg.readable = true; // Set to true if data can be read + cfg.invert = true; // Set to true if the light/darkness of the panel is reversed + cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped + cfg.dlen_16bit = false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI + cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.) + + _panel_instance->config(cfg); + + // Configure settings for touch control. + if (settingsMap[touchscreenModule]) { + if (settingsMap[touchscreenModule] == xpt2046) { + _touch_instance = new lgfx::Touch_XPT2046; + } + auto touch_cfg = _touch_instance->config(); + + touch_cfg.pin_cs = settingsMap[touchscreenCS]; + touch_cfg.x_min = 0; + touch_cfg.x_max = settingsMap[displayHeight] - 1; + touch_cfg.y_min = 0; + touch_cfg.y_max = settingsMap[displayWidth] - 1; + touch_cfg.pin_int = settingsMap[touchscreenIRQ]; + touch_cfg.bus_shared = true; + touch_cfg.offset_rotation = 1; + + _touch_instance->config(touch_cfg); + _panel_instance->setTouch(_touch_instance); + } + + setPanel(_panel_instance); // Sets the panel to use. + } +}; +static LGFX *tft = nullptr; #endif -#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) +#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_RASPBERRY_PI #include "SPILock.h" #include "TFTDisplay.h" #include TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) { -#ifdef SCREEN_ROTATE + LOG_DEBUG("TFTDisplay!\n"); +#if ARCH_RASPBERRY_PI + if (settingsMap[displayRotate]) { + setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]); + } else { + setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]); + } + +#elif defined(SCREEN_ROTATE) setGeometry(GEOMETRY_RAWMODE, TFT_HEIGHT, TFT_WIDTH); #else setGeometry(GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); @@ -346,19 +422,25 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g } // Write the buffer to the display memory -void TFTDisplay::display(void) +void TFTDisplay::display(bool fromBlank) { + if (fromBlank) + tft->clear(); concurrency::LockGuard g(spiLock); uint16_t x, y; for (y = 0; y < displayHeight; y++) { for (x = 0; x < displayWidth; x++) { - // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7)); - auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7)); - if (isset != dblbuf_isset) { - tft.drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK); + if (!fromBlank) { + // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent + auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7)); + if (isset != dblbuf_isset) { + tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK); + } + } else if (isset) { + tft->drawPixel(x, y, TFT_MESH); } } } @@ -377,7 +459,11 @@ void TFTDisplay::sendCommand(uint8_t com) // handle display on/off directly switch (com) { case DISPLAYON: { -#if defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON) +#if ARCH_RASPBERRY_PI + display(true); + if (settingsMap[displayBacklight] > 0) + digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); +#elif defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON) if (heltec_version == 3) { digitalWrite(ST7735_BACKLIGHT_EN_V03, TFT_BACKLIGHT_ON); } else { @@ -400,12 +486,16 @@ void TFTDisplay::sendCommand(uint8_t com) #ifdef RAK14014 #elif !defined(M5STACK) - tft.setBrightness(128); + tft->setBrightness(128); #endif break; } case DISPLAYOFF: { -#if defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON) +#if ARCH_RASPBERRY_PI + tft->clear(); + if (settingsMap[displayBacklight] > 0) + digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); +#elif defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON) if (heltec_version == 3) { digitalWrite(ST7735_BACKLIGHT_EN_V03, !TFT_BACKLIGHT_ON); } else { @@ -427,7 +517,7 @@ void TFTDisplay::sendCommand(uint8_t com) #endif #ifdef RAK14014 #elif !defined(M5STACK) - tft.setBrightness(0); + tft->setBrightness(0); #endif break; } @@ -442,7 +532,7 @@ void TFTDisplay::flipScreenVertically() { #if defined(T_WATCH_S3) LOG_DEBUG("Flip TFT vertically\n"); // T-Watch S3 right-handed orientation - tft.setRotation(0); + tft->setRotation(0); #endif } @@ -450,7 +540,7 @@ bool TFTDisplay::hasTouch(void) { #ifdef RAK14014 #elif !defined(M5STACK) - return tft.touch() != nullptr; + return tft->touch() != nullptr; #else return false; #endif @@ -460,7 +550,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y) { #ifdef RAK14014 #elif !defined(M5STACK) - return tft.getTouch(x, y); + return tft->getTouch(x, y); #else return false; #endif @@ -476,6 +566,11 @@ bool TFTDisplay::connect() { concurrency::LockGuard g(spiLock); LOG_INFO("Doing TFT init\n"); +#ifdef RAK14014 + tft = new TFT_eSPI; +#else + tft = new LGFX; +#endif #ifdef TFT_BL pinMode(TFT_BL, OUTPUT); @@ -495,24 +590,24 @@ bool TFTDisplay::connect() } #endif - tft.init(); + tft->init(); #if defined(M5STACK) - tft.setRotation(0); + tft->setRotation(0); #elif defined(RAK14014) - tft.setRotation(1); - tft.setSwapBytes(true); -// tft.fillScreen(TFT_BLACK); + tft->setRotation(1); + tft->setSwapBytes(true); +// tft->fillScreen(TFT_BLACK); #elif defined(T_DECK) || defined(PICOMPUTER_S3) - tft.setRotation(1); // T-Deck has the TFT in landscape + tft->setRotation(1); // T-Deck has the TFT in landscape #elif defined(T_WATCH_S3) - tft.setRotation(2); // T-Watch S3 left-handed orientation + tft->setRotation(2); // T-Watch S3 left-handed orientation #else - tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label + tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label #endif - tft.fillScreen(TFT_BLACK); + tft->fillScreen(TFT_BLACK); return true; } -#endif +#endif \ No newline at end of file diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 8c9a9b62e7..3d6ea6cc62 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -20,7 +20,8 @@ class TFTDisplay : public OLEDDisplay TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C); // Write the buffer to the display memory - virtual void display(void) override; + virtual void display() override { display(false); }; + virtual void display(bool fromBlank); // Turn the display upside down virtual void flipScreenVertically(); diff --git a/src/graphics/images.h b/src/graphics/images.h index a1191076bd..7f3cd46fcc 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -14,7 +14,7 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3 const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF}; const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF}; -#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ +#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_RASPBERRY_PI) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; @@ -30,4 +30,4 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5}; #endif -#include "img/icon.xbm" +#include "img/icon.xbm" \ No newline at end of file diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp new file mode 100644 index 0000000000..4b61509496 --- /dev/null +++ b/src/input/LinuxInput.cpp @@ -0,0 +1,179 @@ +#if ARCH_RASPBERRY_PI +#include "LinuxInput.h" +#include "configuration.h" + +#include "platform/portduino/PortduinoGlue.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Inspired by https://github.com/librerpi/rpi-tools/blob/master/keyboard-proxy/main.c which is GPL-v2 + +LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name) +{ + this->_originName = name; +} + +int32_t LinuxInput::runOnce() +{ + + if (firstTime) { + if (settingsStrings[keyboardDevice] == "") + return disable(); + fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR); + if (fd < 0) + return disable(); + ret = ioctl(fd, EVIOCGRAB, (void *)1); + if (ret != 0) + return disable(); + + epollfd = epoll_create1(0); + assert(epollfd >= 0); + + ev.events = EPOLLIN; + ev.data.fd = fd; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) { + perror("unable to epoll add"); + return disable(); + } + // This is the first time the OSThread library has called this function, so do port setup + firstTime = 0; + } + + int nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1); + if (nfds < 0) { + printf("%d ", nfds); + perror("epoll_wait failed"); + return disable(); + } else if (nfds == 0) { + return 50; + } + + int keys = 0; + memset(report, 0, 8); + for (int i = 0; i < nfds; i++) { + + struct input_event ev[64]; + int rd = read(events[i].data.fd, ev, sizeof(ev)); + assert(rd > ((signed int)sizeof(struct input_event))); + for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) { + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.source = this->_originName; + e.kbchar = 0; + unsigned int type, code; + type = ev[j].type; + code = ev[j].code; + int value = ev[j].value; + // printf("Event: time %ld.%06ld, ", ev[j].time.tv_sec, ev[j].time.tv_usec); + + if (type == EV_KEY) { + uint8_t mod = 0; + + switch (code) { + case KEY_LEFTCTRL: + mod = 0x01; + break; + case KEY_RIGHTCTRL: + mod = 0x10; + break; + case KEY_LEFTSHIFT: + mod = 0x02; + break; + case KEY_RIGHTSHIFT: + mod = 0x20; + break; + case KEY_LEFTALT: + mod = 0x04; + break; + case KEY_RIGHTALT: + mod = 0x40; + break; + case KEY_LEFTMETA: + mod = 0x08; + break; + } + if (value == 1) { + switch (code) { + case KEY_LEFTCTRL: + mod = 0x01; + break; + case KEY_RIGHTCTRL: + mod = 0x10; + break; + case KEY_LEFTSHIFT: + mod = 0x02; + break; + case KEY_RIGHTSHIFT: + mod = 0x20; + break; + case KEY_LEFTALT: + mod = 0x04; + break; + case KEY_RIGHTALT: + mod = 0x40; + break; + case KEY_LEFTMETA: + mod = 0x08; + break; + case KEY_ESC: // ESC + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; + break; + case KEY_BACK: // Back + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + // e.kbchar = key; + break; + + case KEY_UP: // Up + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + break; + case KEY_DOWN: // Down + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; + break; + case KEY_LEFT: // Left + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; + break; + e.kbchar = 0xb4; + case KEY_RIGHT: // Right + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + break; + e.kbchar = 0xb7; + case KEY_ENTER: // Enter + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; + break; + default: // all other keys + if (keymap[code]) { + e.inputEvent = ANYKEY; + e.kbchar = keymap[code]; + } + break; + } + } + if (ev[j].value) { + modifiers |= mod; + } else { + modifiers &= ~mod; + } + report[0] = modifiers; + } + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + if (e.inputEvent == ANYKEY && (modifiers && 0x22)) + e.kbchar = uppers[e.kbchar]; // doesn't get punctuation. Meh. + this->notifyObservers(&e); + } + } + } + + return 50; // Keyscan every 50msec to avoid key bounce +} + +#endif \ No newline at end of file diff --git a/src/input/LinuxInput.h b/src/input/LinuxInput.h new file mode 100644 index 0000000000..c21fb4c36c --- /dev/null +++ b/src/input/LinuxInput.h @@ -0,0 +1,64 @@ +#pragma once +#if ARCH_RASPBERRY_PI +#include "InputBroker.h" +#include "concurrency/OSThread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_EVENTS 10 + +class LinuxInput : public Observable, public concurrency::OSThread +{ + public: + explicit LinuxInput(const char *name); + + protected: + virtual int32_t runOnce() override; + + private: + const char *_originName; + bool firstTime = 1; + int shift = 0; + char key = 0; + char prevkey = 0; + + InputEvent eventqueue[50]; // The Linux API will return multiple keypresses at a time. Queue them to not miss any. + int queue_length = 0; + int queue_progress = 0; + + struct epoll_event events[MAX_EVENTS]; + int fd; + int ret; + uint8_t report[8]; + int epollfd; + struct epoll_event ev; + uint8_t modifiers = 0; + std::map keymap{ + {KEY_A, 'a'}, {KEY_B, 'b'}, {KEY_C, 'c'}, {KEY_D, 'd'}, {KEY_E, 'e'}, + {KEY_F, 'f'}, {KEY_G, 'g'}, {KEY_H, 'h'}, {KEY_I, 'i'}, {KEY_J, 'j'}, + {KEY_K, 'k'}, {KEY_L, 'l'}, {KEY_M, 'm'}, {KEY_N, 'n'}, {KEY_O, 'o'}, + {KEY_P, 'p'}, {KEY_Q, 'q'}, {KEY_R, 'r'}, {KEY_S, 's'}, {KEY_T, 't'}, + {KEY_U, 'u'}, {KEY_V, 'v'}, {KEY_W, 'w'}, {KEY_X, 'x'}, {KEY_Y, 'y'}, + {KEY_Z, 'z'}, {KEY_BACKSPACE, 0x08}, {KEY_SPACE, ' '}, {KEY_1, '1'}, {KEY_2, '2'}, + {KEY_3, '3'}, {KEY_4, '4'}, {KEY_5, '5'}, {KEY_6, '6'}, {KEY_7, '7'}, + {KEY_8, '8'}, {KEY_9, '9'}, {KEY_0, '0'}, {KEY_DOT, '.'}, {KEY_COMMA, ','}, + {KEY_MINUS, '-'}, {KEY_EQUAL, '='}, {KEY_LEFTBRACE, '['}, {KEY_RIGHTBRACE, ']'}, {KEY_BACKSLASH, '\\'}, + {KEY_SEMICOLON, ';'}, {KEY_APOSTROPHE, '\''}, {KEY_SLASH, '/'}, {KEY_TAB, 0x09}}; + std::map uppers{{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'}, {'f', 'F'}, {'g', 'G'}, {'h', 'H'}, + {'i', 'I'}, {'j', 'J'}, {'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'}, {'p', 'P'}, + {'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'}, {'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'}, + {'y', 'Y'}, {'z', 'Z'}, {'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'}, {'6', '^'}, + {'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'}, {'.', '>'}, {',', '<'}, {'-', '_'}, {'=', '+'}, + {'[', '{'}, {']', '}'}, {'\\', '|'}, {';', ':'}, {'\'', '"'}, {'/', '?'}}; +}; +#endif \ No newline at end of file diff --git a/src/input/LinuxInputImpl.cpp b/src/input/LinuxInputImpl.cpp new file mode 100644 index 0000000000..d12f457ecd --- /dev/null +++ b/src/input/LinuxInputImpl.cpp @@ -0,0 +1,14 @@ +#if ARCH_RASPBERRY_PI +#include "LinuxInputImpl.h" +#include "InputBroker.h" + +LinuxInputImpl *aLinuxInputImpl; + +LinuxInputImpl::LinuxInputImpl() : LinuxInput("LinuxInput") {} + +void LinuxInputImpl::init() +{ + inputBroker->registerSource(this); +} + +#endif \ No newline at end of file diff --git a/src/input/LinuxInputImpl.h b/src/input/LinuxInputImpl.h new file mode 100644 index 0000000000..b5bfdc4c2c --- /dev/null +++ b/src/input/LinuxInputImpl.h @@ -0,0 +1,21 @@ +#ifdef ARCH_RASPBERRY_PI +#pragma once +#include "LinuxInput.h" +#include "main.h" + +/** + * @brief The idea behind this class to have static methods for the event handlers. + * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp + * Technically you can have as many rotary encoders hardver attached + * to your device as you wish, but you always need to have separate event + * handlers, thus you need to have a RotaryEncoderInterrupt implementation. + */ + +class LinuxInputImpl : public LinuxInput +{ + public: + LinuxInputImpl(); + void init(); +}; +extern LinuxInputImpl *aLinuxInputImpl; +#endif \ No newline at end of file diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index b3152c88a3..145033c950 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -2,6 +2,11 @@ #include "InputBroker.h" #include "PowerFSM.h" #include "configuration.h" +#include "modules/ExternalNotificationModule.h" + +#ifdef ARCH_RASPBERRY_PI +#include "platform/portduino/PortduinoGlue.h" +#endif TouchScreenImpl1 *touchScreenImpl1; @@ -12,7 +17,14 @@ TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTo void TouchScreenImpl1::init() { -#if !HAS_TOUCHSCREEN +#if ARCH_RASPBERRY_PI + if (settingsMap[touchscreenModule]) { + TouchScreenBase::init(true); + inputBroker->registerSource(this); + } else { + TouchScreenBase::init(false); + } +#elif !HAS_TOUCHSCREEN TouchScreenBase::init(false); return; #else @@ -63,7 +75,11 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event) break; } case TOUCH_ACTION_TAP: { - powerFSM.trigger(EVENT_INPUT); + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { + externalNotificationModule->stopNow(); + } else { + powerFSM.trigger(EVENT_INPUT); + } break; } default: diff --git a/src/main.cpp b/src/main.cpp index b3671c020c..9c67cc0ace 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,6 +84,11 @@ NRF52Bluetooth *nrf52Bluetooth; #include "AmbientLightingThread.h" #endif +#ifdef HAS_I2S +#include "AudioThread.h" +AudioThread *audioThread; +#endif + using namespace concurrency; // We always create a screen object, but we only init it if we find the hardware @@ -122,6 +127,7 @@ ATECCX08A atecc; #ifdef T_WATCH_S3 Adafruit_DRV2605 drv; #endif + bool isVibrating = false; bool eink_found = true; @@ -432,6 +438,10 @@ void setup() auto i2cCount = i2cScanner->countDevices(); if (i2cCount == 0) { LOG_INFO("No I2C devices found\n"); + Wire.end(); +#ifdef I2C_SDA1 + Wire1.end(); +#endif } else { LOG_INFO("%i I2C devices found\n", i2cCount); } @@ -576,10 +586,13 @@ void setup() // but we need to do this after main cpu init (esp32setup), because we need the random seed set nodeDB.init(); - // If we're taking on the repeater role, use flood router - if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) + // If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed + if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { router = new FloodingRouter(); - else +#ifdef PIN_3V3_EN + digitalWrite(PIN_3V3_EN, LOW); +#endif + } else router = new ReliableRouter(); #if HAS_BUTTON || defined(ARCH_RASPBERRY_PI) @@ -653,7 +666,10 @@ void setup() readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) - gps = GPS::createGps(); + // If we're taking on the repeater role, ignore GPS + if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { + gps = GPS::createGps(); + } if (gps) { gpsStatus->observe(&gps->newStatus); } else { @@ -661,6 +677,11 @@ void setup() } nodeStatus->observe(&nodeDB.newStatus); +#ifdef HAS_I2S + LOG_DEBUG("Starting audio thread\n"); + audioThread = new AudioThread(); +#endif + service.init(); // Now that the mesh service is created, create any modules @@ -676,6 +697,10 @@ void setup() // the current region name) #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) screen->setup(); +#elif ARCH_RASPBERRY_PI + if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { + screen->setup(); + } #else if (screen_found.port != ScanI2C::I2CPort::NO_I2C) screen->setup(); @@ -870,7 +895,6 @@ void setup() // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS powerFSMthread = new PowerFSMThread(); - setCPUFast(false); // 80MHz is fine for our slow peripherals } @@ -941,4 +965,4 @@ void loop() mainDelay.delay(delayMsec); } // if (didWake) LOG_DEBUG("wake!\n"); -} +} \ No newline at end of file diff --git a/src/main.h b/src/main.h index 5c9de1b818..52e9a42715 100644 --- a/src/main.h +++ b/src/main.h @@ -42,6 +42,12 @@ extern ATECCX08A atecc; #include extern Adafruit_DRV2605 drv; #endif + +#ifdef HAS_I2S +#include "AudioThread.h" +extern AudioThread *audioThread; +#endif + extern bool isVibrating; extern int TCPPort; // set by Portduino diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 84cac7effc..d30404b9f0 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -73,58 +73,3 @@ template class MemoryDynamic : public Allocator return p; } }; - -/** - * A pool based allocator - * - */ -template class MemoryPool : public Allocator -{ - PointerQueue dead; - - T *buf; // our large raw block of memory - - size_t maxElements; - - public: - explicit MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements) - { - buf = new T[maxElements]; - - // prefill dead - for (size_t i = 0; i < maxElements; i++) - release(&buf[i]); - } - - ~MemoryPool() { delete[] buf; } - - /// Return a buffer for use by others - void release(T *p) - { - assert(p >= buf && - (size_t)(p - buf) < - maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool - assert(dead.enqueue(p, 0)); - } - -#ifdef HAS_FREE_RTOS - /// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-) - void releaseFromISR(T *p, BaseType_t *higherPriWoken) - { - assert(p >= buf && - (size_t)(p - buf) < - maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool - assert(dead.enqueueFromISR(p, higherPriWoken)); - } -#endif - - protected: - /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you - /// probably don't want this version). - virtual T *alloc(TickType_t maxWait) - { - T *p = dead.dequeuePtr(maxWait); - assert(p); - return p; - } -}; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 4fd9523c0c..9101712d1b 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -140,6 +140,22 @@ void MeshService::reloadOwner(bool shouldSave) } } +// search the queue for a request id and return the matching nodenum +NodeNum MeshService::getNodenumFromRequestId(uint32_t request_id) +{ + NodeNum nodenum = 0; + for (int i = 0; i < toPhoneQueue.numUsed(); i++) { + meshtastic_MeshPacket *p = toPhoneQueue.dequeuePtr(0); + if (p->id == request_id) { + nodenum = p->to; + // make sure to continue this to make one full loop + } + // put it right back on the queue + toPhoneQueue.enqueue(p, 0); + } + return nodenum; +} + /** * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index eb40b77122..6d73c076a4 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -82,6 +82,9 @@ class MeshService /// Return the next MqttClientProxyMessage packet destined to the phone. meshtastic_MqttClientProxyMessage *getMqttClientProxyMessageForPhone() { return toPhoneMqttProxyQueue.dequeuePtr(0); } + // search the queue for a request id and return the matching nodenum + NodeNum getNodenumFromRequestId(uint32_t request_id); + // Release QueueStatus packet to pool void releaseQueueStatusToPool(meshtastic_QueueStatus *p) { queueStatusPool.release(p); } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 7250c6ae60..7712fd92a2 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -27,6 +27,10 @@ #include #endif +#ifdef ARCH_RASPBERRY_PI +#include "platform/portduino/PortduinoGlue.h" +#endif + #ifdef ARCH_NRF52 #include #include @@ -191,6 +195,12 @@ void NodeDB::installDefaultConfig() config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) bool hasScreen = true; +#elif ARCH_RASPBERRY_PI + bool hasScreen = false; + if (settingsMap[displayPanel]) + hasScreen = true; + else + hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; #else bool hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; #endif @@ -245,9 +255,12 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = 60; #endif -#ifdef T_WATCH_S3 - // Don't worry about the other settings, we'll use the DRV2056 behavior for notifications +#ifdef HAS_I2S + // Don't worry about the other settings for T-Watch, we'll also use the DRV2056 behavior for notifications moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.use_i2s_as_buzzer = true; + moduleConfig.external_notification.alert_message_buzzer = true; + moduleConfig.external_notification.nag_timeout = 60; #endif #ifdef NANO_G2_ULTRA moduleConfig.external_notification.enabled = true; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c08f9183b2..c96edae8e3 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -27,6 +27,8 @@ template class TypedQueue bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; } + int numUsed() { return uxQueueMessagesWaiting(h); } + /** euqueue a packet. Also, maxWait used to default to portMAX_DELAY, but we now want to callers to THINK about what blocking * they want */ bool enqueue(T x, TickType_t maxWait) @@ -80,6 +82,8 @@ template class TypedQueue bool isEmpty() { return q.empty(); } + int numUsed() { return q.size(); } + bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { if (reader) { diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index ade9d0e5a4..45d2083b73 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1,6 +1,10 @@ #include "configuration.h" +#if ARCH_RASPBERRY_PI +#include "PortduinoGlue.h" +#endif #if HAS_SCREEN #include "CannedMessageModule.h" +#include "Channels.h" #include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" @@ -141,14 +145,18 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) bool validEvent = false; if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP)) { - // LOG_DEBUG("Canned message event UP\n"); - this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; - validEvent = true; + if (this->messagesCount > 0) { + // LOG_DEBUG("Canned message event UP\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; + validEvent = true; + } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN)) { - // LOG_DEBUG("Canned message event DOWN\n"); - this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; - validEvent = true; + if (this->messagesCount > 0) { + // LOG_DEBUG("Canned message event DOWN\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; + validEvent = true; + } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { LOG_DEBUG("Canned message event Select\n"); @@ -163,9 +171,14 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { LOG_DEBUG("Canned message event Cancel\n"); - // emulate a timeout. Same result - this->lastTouchMillis = 0; - validEvent = true; + UIFrameEvent e = {false, true}; + e.frameChanged = true; + this->currentMessageIndex = -1; + this->freetext = ""; // clear freetext + this->cursor = 0; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->notifyObservers(&e); } if ((event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || @@ -175,10 +188,10 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { this->payload = 0xb4; - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { this->payload = 0xb7; - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } } else { // pass the pressed key @@ -212,16 +225,21 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (validEvent) { // Let runOnce to be called immediately. - setIntervalFromNow(0); + if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { + setIntervalFromNow(0); // on fast keypresses, this isn't fast enough. + } else { + runOnce(); + } } return 0; } -void CannedMessageModule::sendText(NodeNum dest, const char *message, bool wantReplies) +void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies) { meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; + p->channel = channel; p->want_ack = true; p->decoded.payload.size = strlen(message); memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); @@ -233,7 +251,9 @@ void CannedMessageModule::sendText(NodeNum dest, const char *message, bool wantR LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); - service.sendToMesh(p); + service.sendToMesh( + p, RX_SRC_LOCAL, + true); // send to mesh, cc to phone. Even if there's no phone connected, this stores the message to match ACKs } int32_t CannedMessageModule::runOnce() @@ -244,14 +264,15 @@ int32_t CannedMessageModule::runOnce() } // LOG_DEBUG("Check status\n"); UIFrameEvent e = {false, true}; - if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || + (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED)) { // TODO: might have some feedback of sendig state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->notifyObservers(&e); } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { @@ -261,13 +282,13 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->notifyObservers(&e); } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { if (this->freetext.length() > 0) { - sendText(this->dest, this->freetext.c_str(), true); + sendText(this->dest, indexChannels[this->channel], this->freetext.c_str(), true); this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { LOG_DEBUG("Reset message is empty.\n"); @@ -279,7 +300,7 @@ int32_t CannedMessageModule::runOnce() powerFSM.trigger(EVENT_PRESS); return INT32_MAX; } else { - sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true); + sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true); } this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { @@ -291,7 +312,7 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->notifyObservers(&e); return 2000; } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { @@ -304,7 +325,7 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = getPrevIndex(); this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE UP (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } @@ -313,14 +334,14 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = this->getNextIndex(); this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { case 0xb4: // left - if (this->destSelect) { + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB.getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { this->dest = nodeDB.getNodeNum(); @@ -335,6 +356,19 @@ int32_t CannedMessageModule::runOnce() if (this->dest == nodeDB.getNodeNum()) { this->dest = NODENUM_BROADCAST; } + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + for (unsigned int i = 0; i < channels.getNumChannels(); i++) { + if ((channels.getByIndex(i).role == meshtastic_Channel_Role_SECONDARY) || + (channels.getByIndex(i).role == meshtastic_Channel_Role_PRIMARY)) { + indexChannels[numChannels] = i; + numChannels++; + } + } + if (this->channel == 0) { + this->channel = numChannels - 1; + } else { + this->channel--; + } } else { if (this->cursor > 0) { this->cursor--; @@ -342,7 +376,7 @@ int32_t CannedMessageModule::runOnce() } break; case 0xb7: // right - if (this->destSelect) { + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { size_t numMeshNodes = nodeDB.getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { this->dest = nodeDB.getNodeNum(); @@ -357,6 +391,19 @@ int32_t CannedMessageModule::runOnce() if (this->dest == nodeDB.getNodeNum()) { this->dest = NODENUM_BROADCAST; } + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + for (unsigned int i = 0; i < channels.getNumChannels(); i++) { + if ((channels.getByIndex(i).role == meshtastic_Channel_Role_SECONDARY) || + (channels.getByIndex(i).role == meshtastic_Channel_Role_PRIMARY)) { + indexChannels[numChannels] = i; + numChannels++; + } + } + if (this->channel == numChannels - 1) { + this->channel = 0; + } else { + this->channel++; + } } else { if (this->cursor < this->freetext.length()) { this->cursor++; @@ -381,10 +428,12 @@ int32_t CannedMessageModule::runOnce() } break; case 0x09: // tab - if (this->destSelect) { - this->destSelect = false; + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL; } else { - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } break; case 0xb4: // left @@ -483,7 +532,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st { char buffer[50]; - if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + String displayString; + if (this->ack) { + displayString = "Delivered to\n%s"; + } else { + displayString = "Delivery failed\nto %s"; + } + display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, displayString, + cannedMessageModule->getNodeName(this->incoming)); + } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); @@ -494,19 +554,39 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - if (this->destSelect) { + if (this->destSelect != CANNED_MESSAGE_DESTINATION_TYPE_NONE) { display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); - display->drawStringf(1 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); } - display->drawStringf(0 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); - // used chars right aligned - uint16_t charsLeft = - meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); - snprintf(buffer, sizeof(buffer), "%d left", charsLeft); - display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); - if (this->destSelect) { - display->drawString(x + display->getWidth() - display->getStringWidth(buffer) - 1, y + 0, buffer); + switch (this->destSelect) { + case CANNED_MESSAGE_DESTINATION_TYPE_NODE: + display->drawStringf(1 + x, 0 + y, buffer, "To: >%s<@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + display->drawStringf(0 + x, 0 + y, buffer, "To: >%s<@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + break; + case CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL: + display->drawStringf(1 + x, 0 + y, buffer, "To: %s@>%s<", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + display->drawStringf(0 + x, 0 + y, buffer, "To: %s@>%s<", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + break; + default: + if (display->getWidth() > 128) { + display->drawStringf(0 + x, 0 + y, buffer, "To: %s@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + } else { + display->drawStringf(0 + x, 0 + y, buffer, "To: %.5s@%.5s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + } + break; + } + // used chars right aligned, only when not editing the destination + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) { + uint16_t charsLeft = + meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); + snprintf(buffer, sizeof(buffer), "%d left", charsLeft); + display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); } display->setColor(WHITE); display->drawStringMaxWidth( @@ -546,6 +626,27 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } } +ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) { + // look for a request_id + if (mp.decoded.request_id != 0) { + UIFrameEvent e = {false, true}; + e.frameChanged = true; + this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; + this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); + meshtastic_Routing decoded = meshtastic_Routing_init_default; + pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); + this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; + this->notifyObservers(&e); + // run the next time 2 seconds later + setIntervalFromNow(2000); + } + } + + return ProcessMessage::CONTINUE; +} + void CannedMessageModule::loadProtoForModule() { if (!nodeDB.loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, @@ -650,4 +751,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) return result; } -#endif +#endif \ No newline at end of file diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 98467215ed..4802be0781 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -9,11 +9,18 @@ enum cannedMessageModuleRunState { CANNED_MESSAGE_RUN_STATE_ACTIVE, CANNED_MESSAGE_RUN_STATE_FREETEXT, CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE, + CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED, CANNED_MESSAGE_RUN_STATE_ACTION_SELECT, CANNED_MESSAGE_RUN_STATE_ACTION_UP, CANNED_MESSAGE_RUN_STATE_ACTION_DOWN, }; +enum cannedMessageDestinationType { + CANNED_MESSAGE_DESTINATION_TYPE_NONE, + CANNED_MESSAGE_DESTINATION_TYPE_NODE, + CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL +}; + #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 /** * Sum of CannedMessageModuleConfig part sizes. @@ -37,19 +44,33 @@ class CannedMessageModule : public SinglePortModule, public Observabledecoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + case meshtastic_PortNum_ROUTING_APP: + return true; + default: + return false; + } + } + protected: virtual int32_t runOnce() override; - void sendText(NodeNum dest, const char *message, bool wantReplies); + void sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies); int splitConfiguredMessages(); int getNextIndex(); @@ -63,6 +84,12 @@ class CannedMessageModule : public SinglePortModule, public Observable -#include "main.h" - #ifdef HAS_NCP5623 #include @@ -54,6 +53,8 @@ bool ascending = true; #endif #define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000 +#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25 + #define ASCII_BELL 0x07 meshtastic_RTTTLConfig rtttlConfig; @@ -71,7 +72,12 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { - if ((nagCycleCutoff < millis()) && !rtttl::isPlaying()) { + + bool isPlaying = rtttl::isPlaying(); +#ifdef HAS_I2S + isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); +#endif + if ((nagCycleCutoff < millis()) && !isPlaying) { // let the song finish if we reach timeout nagCycleCutoff = UINT32_MAX; LOG_INFO("Turning off external notification: "); @@ -132,6 +138,16 @@ int32_t ExternalNotificationModule::runOnce() #endif } + // Play RTTTL over i2s audio interface if enabled as buzzer +#ifdef HAS_I2S + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + if (audioThread->isPlaying()) { + // Continue playing + } else if (isNagging && (nagCycleCutoff >= millis())) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } + } +#endif // now let the PWM buzzer play if (moduleConfig.external_notification.use_pwm) { if (rtttl::isPlaying()) { @@ -142,7 +158,7 @@ int32_t ExternalNotificationModule::runOnce() } } - return 25; + return EXT_NOTIFICATION_DEFAULT_THREAD_MS; } } @@ -175,6 +191,7 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) digitalWrite(output, (moduleConfig.external_notification.active ? true : false)); break; } + #ifdef HAS_NCP5623 if (rgb_found.type == ScanI2C::NCP5623) { rgb.setColor(red, green, blue); @@ -226,6 +243,9 @@ bool ExternalNotificationModule::getExternal(uint8_t index) void ExternalNotificationModule::stopNow() { rtttl::stop(); +#ifdef HAS_I2S + audioThread->stop(); +#endif nagCycleCutoff = 1; // small value isNagging = false; setIntervalFromNow(0); @@ -246,6 +266,7 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message = true; // moduleConfig.external_notification.alert_message_buzzer = true; // moduleConfig.external_notification.alert_message_vibra = true; + // moduleConfig.external_notification.use_i2s_as_buzzer = true; // moduleConfig.external_notification.active = true; // moduleConfig.external_notification.alert_bell = 1; @@ -255,6 +276,13 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.output_vibra = 28; // RAK4631 IO7 // moduleConfig.external_notification.nag_timeout = 300; + // T-Watch / T-Deck i2s audio as buzzer: + // moduleConfig.external_notification.enabled = true; + // moduleConfig.external_notification.nag_timeout = 300; + // moduleConfig.external_notification.output_ms = 1000; + // moduleConfig.external_notification.use_i2s_as_buzzer = true; + // moduleConfig.external_notification.alert_message_buzzer = true; + if (moduleConfig.external_notification.enabled) { if (!nodeDB.loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg, &rtttlConfig)) { @@ -309,14 +337,13 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { if (moduleConfig.external_notification.enabled) { -#if T_WATCH_S3 +#ifdef T_WATCH_S3 drv.setWaveform(0, 75); drv.setWaveform(1, 56); drv.setWaveform(2, 0); drv.go(); #endif if (getFrom(&mp) != nodeDB.getNodeNum()) { - // 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; @@ -359,7 +386,11 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP if (!moduleConfig.external_notification.use_pwm) { setExternalOn(2); } else { +#ifdef HAS_I2S + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); +#else rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); +#endif } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; @@ -394,10 +425,16 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP if (moduleConfig.external_notification.alert_message_buzzer) { LOG_INFO("externalNotificationModule - Notification Module (Buzzer)\n"); isNagging = true; - if (!moduleConfig.external_notification.use_pwm) { + if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { setExternalOn(2); } else { +#ifdef HAS_I2S + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } +#else rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); +#endif } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 526a1c7d8c..19d6b76d47 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -17,6 +17,9 @@ #include "modules/TextMessageModule.h" #include "modules/TraceRouteModule.h" #include "modules/WaypointModule.h" +#if ARCH_RASPBERRY_PI +#include "input/LinuxInputImpl.h" +#endif #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" #endif @@ -44,7 +47,7 @@ void setupModules() { if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { -#if HAS_BUTTON +#if HAS_BUTTON || ARCH_RASPBERRY_PI inputBroker = new InputBroker(); #endif adminModule = new AdminModule(); @@ -61,7 +64,7 @@ void setupModules() new RemoteHardwareModule(); new ReplyModule(); -#if HAS_BUTTON +#if HAS_BUTTON || ARCH_RASPBERRY_PI rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { delete rotaryEncoderInterruptImpl1; @@ -79,6 +82,10 @@ void setupModules() kbMatrixImpl->init(); #endif // INPUTBROKER_MATRIX_TYPE #endif // HAS_BUTTON +#if ARCH_RASPBERRY_PI + aLinuxInputImpl = new LinuxInputImpl(); + aLinuxInputImpl->init(); +#endif #if HAS_TRACKBALL trackballInterruptImpl1 = new TrackballInterruptImpl1(); trackballInterruptImpl1->init(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 1047ade1d5..9c7b406e9b 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -97,9 +97,9 @@ int32_t EnvironmentTelemetryModule::runOnce() result = lps22hbSensor.runOnce(); if (sht31Sensor.hasSensor()) result = sht31Sensor.runOnce(); - if (ina219Sensor.hasSensor() && !ina219Sensor.isInitialized()) + if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); - if (ina260Sensor.hasSensor() && !ina260Sensor.isInitialized()) + if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); } return result; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index c297395422..dd81929c8f 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -211,6 +211,7 @@ void NRF52Bluetooth::shutdown() // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(0); // Minimum power } bool NRF52Bluetooth::isConnected() @@ -333,4 +334,4 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu LOG_INFO("BLE pairing failed\n"); screen->stopBluetoothPinScreen(); -} +} \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 06e18eb911..b8e9dd9e6e 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -17,7 +17,8 @@ #include #include -std::map settingsMap; +std::map settingsMap; +std::map settingsStrings; #else #include @@ -154,6 +155,28 @@ void portduinoSetup() settingsMap[has_gps] = 1; } } + settingsMap[displayPanel] = no_screen; + if (yamlConfig["Display"]) { + if (yamlConfig["Display"]["Panel"].as("") == "ST7789") + settingsMap[displayPanel] = st7789; + settingsMap[displayHeight] = yamlConfig["Display"]["Height"].as(0); + settingsMap[displayWidth] = yamlConfig["Display"]["Width"].as(0); + settingsMap[displayDC] = yamlConfig["Display"]["DC"].as(-1); + settingsMap[displayCS] = yamlConfig["Display"]["CS"].as(-1); + settingsMap[displayBacklight] = yamlConfig["Display"]["Backlight"].as(-1); + settingsMap[displayReset] = yamlConfig["Display"]["Reset"].as(-1); + settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); + } + settingsMap[touchscreenModule] = no_touchscreen; + if (yamlConfig["Touchscreen"]) { + if (yamlConfig["Touchscreen"]["Module"].as("") == "XPT2046") + settingsMap[touchscreenModule] = xpt2046; + settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); + settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); + } + if (yamlConfig["Input"]) { + settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); + } } catch (YAML::Exception e) { std::cout << "*** Exception " << e.what() << std::endl; @@ -191,6 +214,23 @@ void portduinoSetup() } } + if (settingsMap[displayPanel] != no_screen) { + if (settingsMap[displayCS] > 0) + initGPIOPin(settingsMap[displayCS], gpioChipName); + if (settingsMap[displayDC] > 0) + initGPIOPin(settingsMap[displayDC], gpioChipName); + if (settingsMap[displayBacklight] > 0) + initGPIOPin(settingsMap[displayBacklight], gpioChipName); + if (settingsMap[displayReset] > 0) + initGPIOPin(settingsMap[displayReset], gpioChipName); + } + if (settingsMap[touchscreenModule] != no_touchscreen) { + if (settingsMap[touchscreenCS] > 0) + initGPIOPin(settingsMap[touchscreenCS], gpioChipName); + if (settingsMap[touchscreenIRQ] > 0) + initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); + } + return; #endif @@ -250,8 +290,9 @@ int initGPIOPin(int pinNum, std::string gpioChipName) csPin->setSilent(); gpioBind(csPin); return ERRNO_OK; - } catch (std::invalid_argument &e) { - std::cout << "Warning, cannot claim pin" << gpio_name << std::endl; + } catch (...) { + std::exception_ptr p = std::current_exception(); + std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; return ERRNO_DISABLED; } } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index b942ab46af..046c5d0970 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -2,9 +2,35 @@ #ifdef ARCH_RASPBERRY_PI #include -extern std::map settingsMap; +enum configNames { + use_sx1262, + cs, + irq, + busy, + reset, + dio2_as_rf_switch, + use_rf95, + user, + gpiochip, + has_gps, + touchscreenModule, + touchscreenCS, + touchscreenIRQ, + displayPanel, + displayWidth, + displayHeight, + displayCS, + displayDC, + displayBacklight, + displayReset, + displayRotate, + keyboardDevice +}; +enum { no_screen, st7789 }; +enum { no_touchscreen, xpt2046 }; -enum { use_sx1262, cs, irq, busy, reset, dio2_as_rf_switch, use_rf95, user, gpiochip, has_gps }; +extern std::map settingsMap; +extern std::map settingsStrings; int initGPIOPin(int pinNum, std::string gpioChipname); #endif \ No newline at end of file diff --git a/suppressions.txt b/suppressions.txt index e65afc0bf0..6cbd38d47a 100644 --- a/suppressions.txt +++ b/suppressions.txt @@ -49,4 +49,5 @@ virtualCallInConstructor passedByValue:*/RedirectablePrint.h -internalAstError:*/CrossPlatformCryptoEngine.cpp \ No newline at end of file +internalAstError:*/CrossPlatformCryptoEngine.cpp +uninitMemberVar:*/AudioThread.h \ No newline at end of file diff --git a/variants/portduino/platformio.ini b/variants/portduino/platformio.ini index 5e9428d4e2..cdc32fae53 100644 --- a/variants/portduino/platformio.ini +++ b/variants/portduino/platformio.ini @@ -19,4 +19,5 @@ extends = portduino_base build_flags = ${portduino_base.build_flags} -O0 -lgpiod -I variants/portduino -DARCH_RASPBERRY_PI -lpigpio -lyaml-cpp board = linux_arm lib_deps = ${portduino_base.lib_deps} + https://github.com/jp-bennett/LovyanGFX.git#jp-bennett-patch-1 ; lovyan03/LovyanGFX@^1.1.9 build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index 23066276b8..3493f704fc 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -1,6 +1,7 @@ #if defined(ARCH_RASPBERRY_PI) #define HAS_WIRE 1 #define HAS_SCREEN 1 +#define CANNED_MESSAGE_MODULE_ENABLE 1 #else // Pine64 mode. diff --git a/variants/senselora_rp2040/pins_arduino.h b/variants/senselora_rp2040/pins_arduino.h new file mode 100644 index 0000000000..bb0ee637eb --- /dev/null +++ b/variants/senselora_rp2040/pins_arduino.h @@ -0,0 +1,50 @@ +#pragma once + +#define PIN_A0 (26u) +#define PIN_A1 (27u) +#define PIN_A2 (28u) +#define PIN_A3 (29u) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; + +// LEDs +#define PIN_LED (23u) +#define PIN_LED1 PIN_LED +#define LED_BUILTIN PIN_LED + +#define ADC_RESOLUTION 12 + +// Serial +#define PIN_SERIAL1_TX (0ul) +#define PIN_SERIAL1_RX (1ul) + +#define PIN_SERIAL2_TX (4ul) +#define PIN_SERIAL2_RX (5ul) + +// SPI +#define PIN_SPI0_MISO (16u) +#define PIN_SPI0_MOSI (19u) +#define PIN_SPI0_SCK (18u) +#define PIN_SPI0_SS (17u) + +// Wire +#define PIN_WIRE0_SDA (6u) +#define PIN_WIRE0_SCL (7u) + +#define PIN_WIRE1_SDA (-1) +#define PIN_WIRE1_SCL (-1) + +#define SERIAL_HOWMANY (3u) +#define SPI_HOWMANY (2u) +#define WIRE_HOWMANY (1u) + +static const uint8_t SS = PIN_SPI0_SS; +static const uint8_t MOSI = PIN_SPI0_MOSI; +static const uint8_t MISO = PIN_SPI0_MISO; +static const uint8_t SCK = PIN_SPI0_SCK; + +static const uint8_t SDA = PIN_WIRE0_SDA; +static const uint8_t SCL = PIN_WIRE0_SCL; \ No newline at end of file diff --git a/variants/senselora_rp2040/platformio.ini b/variants/senselora_rp2040/platformio.ini index abf28559e5..3b3253ee8d 100644 --- a/variants/senselora_rp2040/platformio.ini +++ b/variants/senselora_rp2040/platformio.ini @@ -6,9 +6,8 @@ upload_protocol = picotool # add our variants files to the include and src paths build_flags = ${rp2040_base.build_flags} -DSENSELORA_RP2040 - -Ivariants/rpipico + -Ivariants/senselora_rp2040 -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" lib_deps = ${rp2040_base.lib_deps} \ No newline at end of file diff --git a/variants/senselora_rp2040/variant.h b/variants/senselora_rp2040/variant.h index 78f3e8f14e..9eda655210 100644 --- a/variants/senselora_rp2040/variant.h +++ b/variants/senselora_rp2040/variant.h @@ -1,20 +1,9 @@ -// #define RADIOLIB_CUSTOM_ARDUINO 1 -// #define RADIOLIB_TONE_UNSUPPORTED 1 -// #define RADIOLIB_SOFTWARE_SERIAL_UNSUPPORTED 1 - #define ARDUINO_ARCH_AVR -#define USE_SSD1306 1 +#define USE_SSD1306 #define BUTTON_PIN 2 - -#define I2C_SDA1 6 -#define I2C_SCL1 7 - -#define PIN_SPI_MISO (16u) -#define PIN_SPI_MOSI (19u) -#define PIN_SPI_SCK (18u) -#define PIN_SPI_SS (17u) +#define BUTTON_NEED_PULLUP #define LED_PIN PIN_LED @@ -26,13 +15,12 @@ #undef LORA_CS #define USE_RF95 -#define LORA_SCK PIN_SPI_SCK -#define LORA_MISO PIN_SPI_MISO -#define LORA_MOSI PIN_SPI_MOSI -#define LORA_CS PIN_SPI_SS +#define LORA_SCK PIN_SPI0_SCK +#define LORA_MISO PIN_SPI0_MISO +#define LORA_MOSI PIN_SPI0_MOSI +#define LORA_CS PIN_SPI0_SS #define LORA_DIO0 21 #define LORA_DIO1 22 -#define LORA_DIO2 23 -#define LORA_DIO5 24 -#define LORA_RST 20 +#define LORA_DIO2 RADIOLIB_NC +#define LORA_RESET 20 \ No newline at end of file diff --git a/variants/t-deck/platformio.ini b/variants/t-deck/platformio.ini index 38e334a304..cb60333005 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/t-deck/platformio.ini @@ -2,8 +2,8 @@ [env:t-deck] extends = esp32s3_base board = t-deck -upload_protocol = esp-builtin -debug_tool = esp-builtin +upload_protocol = esptool +#upload_port = COM29 build_flags = ${esp32_base.build_flags} -DT_DECK @@ -12,4 +12,6 @@ build_flags = ${esp32_base.build_flags} -Ivariants/t-deck lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.9 \ No newline at end of file + lovyan03/LovyanGFX@^1.1.9 + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 \ No newline at end of file diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index 446e227324..62ac0a373f 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -65,6 +65,12 @@ #define ES7210_LRCK 21 #define ES7210_MCLK 48 +// dac / amp +#define HAS_I2S +#define DAC_I2S_BCK 7 +#define DAC_I2S_WS 5 +#define DAC_I2S_DOUT 6 + // LoRa #define USE_SX1262 #define USE_SX1268 diff --git a/variants/t-watch-s3/platformio.ini b/variants/t-watch-s3/platformio.ini index 162384bfd3..d03273ed44 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/t-watch-s3/platformio.ini @@ -3,6 +3,8 @@ extends = esp32s3_base board = t-watch-s3 upload_protocol = esptool +upload_speed = 115200 +upload_port = /dev/tty.usbmodem3485188D636C1 build_flags = ${esp32_base.build_flags} -DT_WATCH_S3 @@ -12,4 +14,6 @@ build_flags = ${esp32_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@^1.1.9 lewisxhe/PCF8563_Library@1.0.1 - adafruit/Adafruit DRV2605 Library@^1.2.2 \ No newline at end of file + adafruit/Adafruit DRV2605 Library@^1.2.2 + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 \ No newline at end of file diff --git a/variants/t-watch-s3/variant.h b/variants/t-watch-s3/variant.h index c302240343..c66fac5efa 100644 --- a/variants/t-watch-s3/variant.h +++ b/variants/t-watch-s3/variant.h @@ -30,6 +30,11 @@ #define TFT_BL ST7789_BACKLIGHT_EN +#define HAS_I2S +#define DAC_I2S_BCK 48 +#define DAC_I2S_WS 15 +#define DAC_I2S_DOUT 46 + #define HAS_AXP2101 #define HAS_RTC 1 @@ -37,8 +42,6 @@ #define I2C_SDA 10 // For QMC6310 sensors and screens #define I2C_SCL 11 // For QMC6310 sensors and screens -#define BUTTON_PIN 0 - #define BMA4XX_INT 14 // Interrupt for BMA_423 axis sensor #define HAS_GPS 0