diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index e4ef3b443..947b1e054 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread #endif #endif pixels.show(); - LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", - moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", + // moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + // moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 5cb4fca32..01a630b52 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = AHT10; break; #endif +#if !defined(M5STACK_UNITC6L) case INA_ADDR: case INA_ADDR_ALTERNATE: case INA_ADDR_WAVESHARE_UPS: @@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) // else: probably a RAK12500/UBLOX GPS on I2C } break; +#endif case MCP9808_ADDR: // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips. diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index dea08d5ba..0a2229d0e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -317,6 +317,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #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(USE_SPISSD1306) + dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48); + if (!dispdev->init()) { + LOG_DEBUG("Error: SSD1306 not detected!"); + } else { + static_cast(dispdev)->setHorizontalOffset(32); + LOG_INFO("SSD1306 init success"); + } #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, @@ -507,7 +515,7 @@ void Screen::setup() // === Apply loaded brightness === #if defined(ST7789_CS) static_cast(dispdev)->setDisplayBrightness(brightness); -#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) +#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306) dispdev->setBrightness(brightness); #endif LOG_INFO("Applied screen brightness: %d", brightness); @@ -554,7 +562,7 @@ void Screen::setup() static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); -#else +#elif !defined(M5STACK_UNITC6L) dispdev->flipScreenVertically(); #endif } @@ -692,7 +700,11 @@ int32_t Screen::runOnce() #ifndef DISABLE_WELCOME_UNSET if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { +#if defined(M5STACK_UNITC6L) + menuHandler::LoraRegionPicker(); +#else menuHandler::OnboardMessage(); +#endif } #endif if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { @@ -890,8 +902,12 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) fsi.positions.clock = numframes; +#if defined(M5STACK_UNITC6L) + normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame; +#else normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame : graphics::ClockRenderer::drawDigitalClockFrame; +#endif indicatorIcons.push_back(digital_icon_clock); #endif @@ -1226,6 +1242,10 @@ void Screen::handleShowNextFrame() void Screen::setFastFramerate() { +#if defined(M5STACK_UNITC6L) + dispdev->clear(); + dispdev->display(); +#endif // We are about to start a transition so speed up fps targetFramerate = SCREEN_TRANSITION_FRAMERATE; @@ -1297,13 +1317,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) } } else { if (longName && longName[0]) { +#if defined(M5STACK_UNITC6L) + strcpy(banner, "New Message"); +#else snprintf(banner, sizeof(banner), "New Message from\n%s", longName); +#endif + } else { strcpy(banner, "New Message"); } } - +#if defined(M5STACK_UNITC6L) + screen->setOn(true); + screen->showSimpleBanner(banner, 1500); + playLongBeep(); +#else screen->showSimpleBanner(banner, 3000); +#endif } } @@ -1386,7 +1416,11 @@ int Screen::handleInputEvent(const InputEvent *event) if (devicestate.rx_text_message.from) { menuHandler::messageResponseMenu(); } else { +#if defined(M5STACK_UNITC6L) + menuHandler::textMessageMenu(); +#else menuHandler::textMessageBaseMenu(); +#endif } } else if (framesetInfo.positions.firstFavorite != 255 && this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite && diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 265900131..ecc39ac60 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -81,6 +81,8 @@ class Screen #include #elif defined(USE_ST7789) #include +#elif defined(USE_SPISSD1306) +#include #else // the SH1106/SSD1306 variant is auto-detected #include diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index a25417b05..c497a27b2 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -79,6 +79,10 @@ #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 +#elif defined(M5STACK_UNITC6L) +#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 +#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13 +#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13 #else #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index b458e54e4..13691665a 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -124,7 +124,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti int batteryX = 1; int batteryY = HEADER_OFFSET_Y + 1; - +#if !defined(M5STACK_UNITC6L) // === Battery Icons === if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging batteryX += 1; @@ -337,7 +337,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti } } } - +#endif display->setColor(WHITE); // Reset for other UI } diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index d5835a335..6137ddef8 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds); // Line 1 (Still) +#if !defined(M5STACK_UNITC6L) display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); if (config.display.heading_bold) display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); display->setColor(WHITE); - +#endif // Setup string to assemble analogClock string std::string analogClock = ""; @@ -386,17 +387,24 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, char shortnameble[35]; getMacAddr(dmac); snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]); +#if defined(M5STACK_UNITC6L) + snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId); +#else snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId); +#endif int textWidth = display->getStringWidth(shortnameble); int nameX = (SCREEN_WIDTH - textWidth); display->drawString(nameX, getTextPositions(display)[line++], shortnameble); - // === Second Row: Radio Preset === auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); char regionradiopreset[25]; const char *region = myRegion ? myRegion->name : NULL; if (region != nullptr) { +#if defined(M5STACK_UNITC6L) + snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region); +#else snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode); +#endif } textWidth = display->getStringWidth(regionradiopreset); nameX = (SCREEN_WIDTH - textWidth) / 2; @@ -408,9 +416,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, float freq = RadioLibInterface::instance->getFreq(); snprintf(freqStr, sizeof(freqStr), "%.3f", freq); if (config.lora.channel_num == 0) { +#if defined(M5STACK_UNITC6L) + snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr); +#else snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr); +#endif } else { +#if defined(M5STACK_UNITC6L) + snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num); +#else snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); +#endif } size_t len = strlen(frequencyslot); if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) { @@ -420,6 +436,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); +#if !defined(M5STACK_UNITC6L) // === Fourth Row: Channel Utilization === const char *chUtil = "ChUtil:"; char chUtilPercentage[10]; @@ -476,6 +493,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4], chUtilPercentage); +#endif } // **************************** @@ -501,8 +519,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, #ifdef USE_EINK barsOffset -= 12; #endif +#if defined(M5STACK_UNITC6L) + const int barX = x + 45 + barsOffset; +#else const int barX = x + 40 + barsOffset; - +#endif auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) { if (total == 0) return; @@ -527,7 +548,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, // Label display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(labelX, getTextPositions(display)[line], label); - +#if !defined(M5STACK_UNITC6L) // Bar int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; display->setColor(WHITE); @@ -535,7 +556,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->fillRect(barX, barY, fillWidth, barHeight); display->setColor(WHITE); - +#endif // Value string display->setTextAlignment(TEXT_ALIGN_RIGHT); display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr); @@ -588,10 +609,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, line += 1; } line += 1; + char appversionstr[35]; snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION)); char appversionstr_formatted[40]; char *lastDot = strrchr(appversionstr, '.'); +#if defined(M5STACK_UNITC6L) + if (lastDot != nullptr) { + *lastDot = '\0'; // truncate string + } +#else if (lastDot) { size_t prefixLen = lastDot - appversionstr; strncpy(appversionstr_formatted, appversionstr, prefixLen); @@ -602,10 +629,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1); appversionstr[sizeof(appversionstr) - 1] = '\0'; } +#endif int textWidth = display->getStringWidth(appversionstr); int nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, getTextPositions(display)[line], appversionstr); + display->drawString(nameX, getTextPositions(display)[line], appversionstr); +#if !defined(M5STACK_UNITC6L) if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it line += 1; char uptimeStr[32] = ""; @@ -624,6 +653,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line], uptimeStr); } +#endif } } // namespace DebugRenderer } // namespace graphics diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index bcd8d8ee8..ba554dbd6 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -79,7 +79,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration) "NP_865", "BR_902"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "LoRa Region"; +#else bannerOptions.message = "Set the LoRa region"; +#endif bannerOptions.durationMs = duration; bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 27; @@ -260,7 +264,11 @@ void menuHandler::TZPicker() void menuHandler::clockMenu() { +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[] = {"Back", "Time Format", "Timezone"}; +#else static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; +#endif enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "Clock Action"; @@ -284,8 +292,11 @@ void menuHandler::clockMenu() void menuHandler::messageResponseMenu() { enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 }; - +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"}; +#else static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"}; +#endif static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset}; int options = 3; @@ -299,7 +310,11 @@ void menuHandler::messageResponseMenu() optionsEnumArray[options++] = Aloud; #endif BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Message"; +#else bannerOptions.message = "Message Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -349,7 +364,11 @@ void menuHandler::homeBaseMenu() optionsArray[options] = "Send Position"; optionsEnumArray[options++] = Position; +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "New Preset"; +#else optionsArray[options] = "New Preset Msg"; +#endif optionsEnumArray[options++] = Preset; if (kb_found) { optionsArray[options] = "New Freetext Msg"; @@ -357,7 +376,11 @@ void menuHandler::homeBaseMenu() } BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Home"; +#else bannerOptions.message = "Home Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -396,6 +419,11 @@ void menuHandler::homeBaseMenu() screen->showOverlayBanner(bannerOptions); } +void menuHandler::textMessageMenu() +{ + cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); +} + void menuHandler::textMessageBaseMenu() { enum optionsNumbers { Back, Preset, Freetext, enumEnd }; @@ -439,11 +467,17 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Screen Options"; optionsEnumArray[options++] = ScreenOptions; #endif - +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "Bluetooth"; +#else optionsArray[options] = "Bluetooth Toggle"; +#endif optionsEnumArray[options++] = Bluetooth; - +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "Power"; +#else optionsArray[options] = "Reboot/Shutdown"; +#endif optionsEnumArray[options++] = PowerMenu; if (test_enabled) { @@ -452,7 +486,11 @@ void menuHandler::systemBaseMenu() } BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "System"; +#else bannerOptions.message = "System Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = options; bannerOptions.optionsEnumPtr = optionsEnumArray; @@ -485,7 +523,11 @@ void menuHandler::systemBaseMenu() void menuHandler::favoriteBaseMenu() { enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd }; +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[enumEnd] = {"Back", "New Preset"}; +#else static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"}; +#endif static int optionsEnumArray[enumEnd] = {Back, Preset}; int options = 2; @@ -493,13 +535,19 @@ void menuHandler::favoriteBaseMenu() optionsArray[options] = "New Freetext Msg"; optionsEnumArray[options++] = Freetext; } +#if !defined(M5STACK_UNITC6L) optionsArray[options] = "Trace Route"; optionsEnumArray[options++] = TraceRoute; +#endif optionsArray[options] = "Remove Favorite"; optionsEnumArray[options++] = Remove; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Favorites"; +#else bannerOptions.message = "Favorites Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -554,11 +602,19 @@ void menuHandler::positionBaseMenu() void menuHandler::nodeListMenu() { enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd }; +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"}; +#else static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"}; +#endif BannerOverlayOptions bannerOptions; bannerOptions.message = "Node Action"; bannerOptions.optionsArrayPtr = optionsArray; +#if defined(M5STACK_UNITC6L) + bannerOptions.optionsCount = 3; +#else bannerOptions.optionsCount = 5; +#endif bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Favorite) { menuQueue = add_favorite; @@ -665,7 +721,11 @@ void menuHandler::BluetoothToggleMenu() { static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Bluetooth"; +#else bannerOptions.message = "Toggle Bluetooth"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { @@ -857,7 +917,11 @@ void menuHandler::rebootMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Reboot"; +#else bannerOptions.message = "Reboot Device?"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { @@ -877,7 +941,11 @@ void menuHandler::shutdownMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Shutdown"; +#else bannerOptions.message = "Shutdown Device?"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { @@ -894,7 +962,12 @@ void menuHandler::shutdownMenu() void menuHandler::addFavoriteMenu() { +#if defined(M5STACK_UNITC6L) + screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void { +#else screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void { + +#endif LOG_WARN("Nodenum: %u", nodenum); nodeDB->set_favorite(true, nodenum); screen->setFrames(graphics::Screen::FOCUS_PRESERVE); @@ -1090,7 +1163,11 @@ void menuHandler::powerMenu() #endif BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Power"; +#else bannerOptions.message = "Reboot / Shutdown"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = options; bannerOptions.optionsEnumPtr = optionsEnumArray; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index b15cf237d..ed49a89fb 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -76,6 +76,7 @@ class menuHandler static void notificationsMenu(); static void screenOptionsMenu(); static void powerMenu(); + static void textMessageMenu(); private: static void saveUIConfig(); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 117829167..6971826de 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->clear(); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - +#if defined(M5STACK_UNITC6L) + const int fixedTopHeight = 24; + const int windowX = 0; + const int windowY = fixedTopHeight; + const int windowWidth = 64; + const int windowHeight = SCREEN_HEIGHT - fixedTopHeight; +#else const int navHeight = FONT_HEIGHT_SMALL; const int scrollBottom = SCREEN_HEIGHT - navHeight; const int usableHeight = scrollBottom; const int textWidth = SCREEN_WIDTH; +#endif bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); bool isBold = config.display.heading_bold; @@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 graphics::drawCommonHeader(display, x, y, titleStr); const char *messageString = "No messages"; int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2); +#if defined(M5STACK_UNITC6L) + display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString); +#else display->drawString(center_text, getTextPositions(display)[2], messageString); +#endif return; } @@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); char headerStr[80]; const char *sender = "???"; +#if defined(M5STACK_UNITC6L) + if (node && node->has_user) + sender = node->user.short_name; +#else if (node && node->has_user) { if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) { sender = node->user.long_name; @@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 sender = node->user.short_name; } } +#endif uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24; uint8_t timestampHours, timestampMinutes; int32_t daysAgo; @@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 sender); } } else { +#if defined(M5STACK_UNITC6L) + snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), + sender); +#else snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), sender); +#endif } +#if defined(M5STACK_UNITC6L) + graphics::drawCommonHeader(display, x, y, titleStr); + int headerY = getTextPositions(display)[1]; + display->drawString(x, headerY, headerStr); + for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) { + display->setPixel(separatorX, fixedTopHeight - 1); + } + cachedLines.clear(); + std::string fullMsg(messageBuf); + std::string currentLine; + for (size_t i = 0; i < fullMsg.size();) { + unsigned char c = fullMsg[i]; + size_t charLen = 1; + if ((c & 0xE0) == 0xC0) + charLen = 2; + else if ((c & 0xF0) == 0xE0) + charLen = 3; + else if ((c & 0xF8) == 0xF0) + charLen = 4; + std::string nextChar = fullMsg.substr(i, charLen); + std::string testLine = currentLine + nextChar; + if (display->getStringWidth(testLine.c_str()) > windowWidth) { + cachedLines.push_back(currentLine); + currentLine = nextChar; + } else { + currentLine = testLine; + } + i += charLen; + } + if (!currentLine.empty()) + cachedLines.push_back(currentLine); + cachedHeights = calculateLineHeights(cachedLines, emotes); + int yOffset = windowY; + int linesDrawn = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + if (linesDrawn >= 2) + break; + int lineHeight = cachedHeights[i]; + if (yOffset + lineHeight > windowY + windowHeight) + break; + drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes); + yOffset += lineHeight; + linesDrawn++; + } + screen->forceDisplay(); +#else uint32_t now = millis(); #ifndef EXCLUDE_EMOJI // === Bounce animation setup === @@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Draw header at the end to sort out overlapping elements graphics::drawCommonHeader(display, x, y, titleStr); +#endif } std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index d8746fb69..7d6a38dd3 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str); // Global screen instance extern graphics::Screen *screen; +#if defined(M5STACK_UNITC6L) +static uint32_t lastSwitchTime = 0; +#else +#endif namespace graphics { namespace NodeListRenderer @@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t { const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1; const int rowYOffset = FONT_HEIGHT_SMALL - 3; - +#if defined(M5STACK_UNITC6L) + int columnWidth = display->getWidth(); +#else int columnWidth = display->getWidth() / 2; - +#endif display->clear(); // Draw the battery/time header @@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int visibleNodeRows = totalRowsAvailable; +#if defined(M5STACK_UNITC6L) + int totalColumns = 1; +#else int totalColumns = 2; - +#endif int startIndex = scrollIndex * visibleNodeRows * totalColumns; if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) { startIndex++; // skip own node @@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t } } +#if !defined(M5STACK_UNITC6L) // Draw column separator if (shownCount > 0) { const int firstNodeY = y + 3; drawColumnSeparator(display, x, firstNodeY, lastNodeY); } +#endif const int scrollStartY = y + 3; drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY); } @@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, unsigned long now = millis(); +#if defined(M5STACK_UNITC6L) + display->clear(); + if (now - lastSwitchTime >= 3000) { + display->display(); + lastSwitchTime = now; + } +#endif // On very first call (on boot or state enter) if (lastRenderedMode == MODE_COUNT) { currentMode = MODE_LAST_HEARD; @@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourNode->position.latitude_i); double lon = DegD(ourNode->position.longitude_i); +#if defined(M5STACK_UNITC6L) + display->clear(); + uint32_t now = millis(); + if (now - lastSwitchTime >= 2000) { + display->display(); + lastSwitchTime = now; + } +#endif if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { #if HAS_GPS if (screen->hasHeading()) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 3d635e588..c2bd1ba66 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -459,6 +459,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay // count lines uint16_t boxWidth = hPadding * 2 + maxWidth; +#if defined(M5STACK_UNITC6L) + if (needs_bell) { + if (isHighResolution && boxWidth <= 150) + boxWidth += 26; + if (!isHighResolution && boxWidth <= 100) + boxWidth += 20; + } + + uint16_t screenHeight = display->height(); + uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3; + uint8_t visibleTotalLines = std::min(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight); + uint16_t contentHeight = visibleTotalLines * effectiveLineHeight; + uint16_t boxHeight = contentHeight + vPadding * 2; + if (visibleTotalLines == 1) + boxHeight += (isHighResolution ? 4 : 3); + + int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); + if (totalLines > visibleTotalLines) + boxWidth += (isHighResolution ? 4 : 2); + int16_t boxTop = (display->height() / 2) - (boxHeight / 2); + + if (visibleTotalLines == 1) { + boxTop += 25; + } + if (alertBannerOptions < 3) { + int missingLines = 3 - alertBannerOptions; + int moveUp = missingLines * (effectiveLineHeight / 2); + boxTop -= moveUp; + if (boxTop < 0) + boxTop = 0; + } + + // === Draw Box === + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, boxWidth, boxHeight); + display->setColor(WHITE); + display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); + display->fillRect(boxLeft, boxTop - 2, boxWidth, 1); + display->fillRect(boxLeft - 2, boxTop, 1, boxHeight); + display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight); + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, 1, 1); + display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1); + display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1); + display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1); + display->setColor(WHITE); + int16_t lineY = boxTop + vPadding; + int swingRange = 8; + static int swingOffset = 0; + static bool swingRight = true; + static unsigned long lastSwingTime = 0; + unsigned long now = millis(); + int swingSpeedMs = 10 / (swingRange * 2); + if (now - lastSwingTime >= (unsigned long)swingSpeedMs) { + lastSwingTime = now; + if (swingRight) { + swingOffset++; + if (swingOffset >= swingRange) + swingRight = false; + } else { + swingOffset--; + if (swingOffset <= 0) + swingRight = true; + } + } + for (int i = 0; i < lineCount; i++) { + bool isTitle = (i == 0); + int globalOptionIndex = (i - 1) + firstOptionToShow; + bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected); + + uint16_t visibleWidth = 64 - hPadding * 2; + if (totalLines > visibleTotalLines) + visibleWidth -= 6; + char lineBuffer[lineLengths[i] + 1]; + strncpy(lineBuffer, lines[i], lineLengths[i]); + lineBuffer[lineLengths[i]] = '\0'; + + if (isTitle) { + if (visibleTotalLines == 1) { + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight); + display->setColor(WHITE); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); + } else { + display->setColor(WHITE); + display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight); + display->setColor(BLACK); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); + display->setColor(WHITE); + if (needs_bell) { + int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2; + display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert); + display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert); + } + } + lineY = boxTop + effectiveLineHeight + 1; + } else if (isSelectedOption) { + display->setColor(WHITE); + display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight); + display->setColor(BLACK); + if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) { + int textX = boxLeft + hPadding + swingOffset; + display->drawString(textX, lineY - 1, lineBuffer); + } else { + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer); + } + display->setColor(WHITE); + lineY += effectiveLineHeight; + } else { + display->setColor(BLACK); + display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight); + display->setColor(WHITE); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer); + lineY += effectiveLineHeight; + } + } + if (totalLines > visibleTotalLines) { + const uint8_t scrollBarWidth = 5; + int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2; + int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; + uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight; + float ratio = (float)visibleTotalLines / totalLines; + uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4); + float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines); + uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight); + display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); + display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); + } +#else if (needs_bell) { if (isHighResolution && boxWidth <= 150) boxWidth += 26; @@ -547,6 +676,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); } +#endif } /// Draw the last text message we received diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 049722df8..e00b19b2f 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -20,7 +20,7 @@ // External variables extern graphics::Screen *screen; - +static uint32_t lastSwitchTime = 0; namespace graphics { NodeNum UIRenderer::currentFavoriteNodeNum = 0; @@ -218,7 +218,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes // ********************** void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) { - if (favoritedNodes.empty()) return; @@ -230,8 +229,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex]; if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) return; - + uint32_t now = millis(); display->clear(); +#if defined(M5STACK_UNITC6L) + if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒 + { + display->display(); + lastSwitchTime = now; + } +#endif currentFavoriteNodeNum = node->num; // === Create the shortName and title string === const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node"; @@ -250,9 +256,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // List of available macro Y positions in order, from top to bottom. int line = 1; // which slot to use next std::string usernameStr; - // === 1. Long Name (always try to show first) === +#if defined(M5STACK_UNITC6L) + const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr; +#else const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr; +#endif + if (username) { usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case // Print node's long name (e.g. "Backpack Node") @@ -307,7 +317,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (seenStr[0] && line < 5) { display->drawString(x, getTextPositions(display)[line++], seenStr); } - +#if !defined(M5STACK_UNITC6L) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { @@ -479,6 +489,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st } // else show nothing } +#endif } // **************************** @@ -492,7 +503,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta int line = 1; // === Header === +#if defined(M5STACK_UNITC6L) + graphics::drawCommonHeader(display, x, y, "Home"); +#else graphics::drawCommonHeader(display, x, y, ""); +#endif // === Content below header === @@ -507,20 +522,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta config.display.heading_bold = false; // Display Region and Channel Utilization +#if defined(M5STACK_UNITC6L) + drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); +#else drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); - +#endif char uptimeStr[32] = ""; uint32_t uptime = millis() / 1000; uint32_t days = uptime / 86400; uint32_t hours = (uptime % 86400) / 3600; uint32_t mins = (uptime % 3600) / 60; // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" +#if !defined(M5STACK_UNITC6L) if (days) snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours); else if (hours) snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins); else snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins); +#endif display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); // === Second Row: Satellites and Voltage === @@ -549,6 +569,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } #endif +#if defined(M5STACK_UNITC6L) + line += 1; + + // === Node Identity === + int textWidth = 0; + int nameX = 0; + char shortnameble[35]; + snprintf(shortnameble, sizeof(shortnameble), "%s", + graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : ""); + + // === ShortName Centered === + textWidth = display->getStringWidth(shortnameble); + nameX = (SCREEN_WIDTH - textWidth) / 2; + display->drawString(nameX, getTextPositions(display)[line++], shortnameble); +#else if (powerStatus->getHasBattery()) { char batStr[20]; int batV = powerStatus->getBatteryVoltageMv() / 1000; @@ -674,6 +709,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], shortnameble); } +#endif } // Start Functions to write date/time to the screen @@ -832,6 +868,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED // needs to be drawn relative to x and y // draw centered icon left to right and centered above the one line of app text +#if defined(M5STACK_UNITC6L) + display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); + display->setFont(FONT_MEDIUM); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + // Draw region in upper left + if (upperMsg) { + int msgWidth = display->getStringWidth(upperMsg); + int msgX = x + (SCREEN_WIDTH - msgWidth) / 2; + int msgY = y; + display->drawString(msgX, msgY, upperMsg); + } + // Draw version and short name in bottom middle + char buf[25]; + snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT), + graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf); + screen->forceDisplay(); + + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code +#else display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, icon_width, icon_height, icon_bits); @@ -840,7 +898,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED const char *title = "meshtastic.org"; display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); display->setFont(FONT_SMALL); - // Draw region in upper left if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); @@ -855,6 +912,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED screen->forceDisplay(); display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code +#endif } // **************************** @@ -930,15 +988,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); char fullLine[40]; snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); +#if !defined(M5STACK_UNITC6L) display->drawString(0, getTextPositions(display)[line++], fullLine); +#endif // === Third Row: Latitude === char latStr[32]; +#if defined(M5STACK_UNITC6L) + snprintf(latStr, sizeof(latStr), "Lat:%.5f", geoCoord.getLatitude() * 1e-7); + display->drawString(x, getTextPositions(display)[line++] + 2, latStr); +#else snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7); display->drawString(x, getTextPositions(display)[line++], latStr); +#endif // === Fourth Row: Longitude === char lonStr[32]; +#if defined(M5STACK_UNITC6L) + snprintf(lonStr, sizeof(lonStr), "Lon:%.3f", geoCoord.getLongitude() * 1e-7); + display->drawString(x, getTextPositions(display)[line++] + 4, lonStr); +#else snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7); display->drawString(x, getTextPositions(display)[line++], lonStr); @@ -950,8 +1019,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude()); } display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo); +#endif } - +#if !defined(M5STACK_UNITC6L) // === Draw Compass if heading is valid === if (validHeading) { // --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- @@ -1034,6 +1104,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } } #endif +#endif } #ifdef USERPREFS_OEM_TEXT @@ -1190,7 +1261,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta display->setColor(WHITE); } } - // Knock the corners off the square display->setColor(BLACK); display->drawRect(rectX, y - 2, 1, 1); diff --git a/src/graphics/images.h b/src/graphics/images.h index c66e4b992..4a58edb3b 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -287,6 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101 #define analog_icon_clock_height 8 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000, 0b00100100, 0b01000010, 0b01000010, 0b11111111}; - +#ifdef M5STACK_UNITC6L +#include "img/icon_small.xbm" +#else #include "img/icon.xbm" +#endif static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning"); \ No newline at end of file diff --git a/src/graphics/img/icon_small.xbm b/src/graphics/img/icon_small.xbm new file mode 100644 index 000000000..e320a1fea --- /dev/null +++ b/src/graphics/img/icon_small.xbm @@ -0,0 +1,30 @@ +#ifndef USERPREFS_HAS_SPLASH +#define icon_width 50 +#define icon_height 20 +static uint8_t icon_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80, + 0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f, + 0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0, + 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f, + 0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00, + 0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00, + 0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00, + 0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe, + 0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f, + 0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0, + 0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03, + 0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00, + 0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0, + 0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03, + 0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8, + 0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00, + 0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00, + 0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc, + 0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00, + 0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38, + 0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; +#endif \ No newline at end of file diff --git a/src/input/i2cButton.cpp b/src/input/i2cButton.cpp new file mode 100644 index 000000000..d874146cd --- /dev/null +++ b/src/input/i2cButton.cpp @@ -0,0 +1,95 @@ +#include "i2cButton.h" +#include "meshUtils.h" + +#include "configuration.h" +#if defined(M5STACK_UNITC6L) + +#include "MeshService.h" +#include "RadioLibInterface.h" +#include "buzz.h" +#include "input/InputBroker.h" +#include "main.h" +#include "modules/CannedMessageModule.h" +#include "modules/ExternalNotificationModule.h" +#include "power.h" +#include "sleep.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + +i2cButtonThread *i2cButton; + +using namespace concurrency; + +extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value); + +extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value); + +#define PI4IO_M_ADDR 0x43 +#define getbit(x, y) ((x) >> (y)&0x01) +#define PI4IO_REG_IRQ_STA 0x13 +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_CHIP_RESET 0x01 + +i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name) +{ + _originName = name; + if (inputBroker) + inputBroker->registerSource(this); +} + +int32_t i2cButtonThread::runOnce() +{ + static bool btn1_pressed = false; + static uint32_t press_start_time = 0; + const uint32_t LONG_PRESS_TIME = 1000; + static bool long_press_triggered = false; + + uint8_t in_data; + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data); + if (getbit(in_data, 0)) { + uint8_t input_state; + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state); + + if (!getbit(input_state, 0)) { + if (!btn1_pressed) { + btn1_pressed = true; + press_start_time = millis(); + long_press_triggered = false; + } + } else { + if (btn1_pressed) { + btn1_pressed = false; + uint32_t press_duration = millis() - press_start_time; + if (long_press_triggered) { + long_press_triggered = false; + return 50; + } + + if (press_duration < LONG_PRESS_TIME) { + InputEvent evt; + evt.source = "UserButton"; + evt.inputEvent = INPUT_BROKER_USER_PRESS; + evt.kbchar = 0; + evt.touchX = 0; + evt.touchY = 0; + this->notifyObservers(&evt); + } + } + } + } + + if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) { + long_press_triggered = true; + InputEvent evt; + evt.source = "UserButton"; + evt.inputEvent = INPUT_BROKER_SELECT; + evt.kbchar = 0; + evt.touchX = 0; + evt.touchY = 0; + this->notifyObservers(&evt); + } + return 50; +} +#endif \ No newline at end of file diff --git a/src/input/i2cButton.h b/src/input/i2cButton.h new file mode 100644 index 000000000..1ad908606 --- /dev/null +++ b/src/input/i2cButton.h @@ -0,0 +1,18 @@ +#pragma once + +#include "InputBroker.h" +#include "OneButton.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#if defined(M5STACK_UNITC6L) + +class i2cButtonThread : public Observable, public concurrency::OSThread +{ + public: + const char *_originName; + explicit i2cButtonThread(const char *name); + int32_t runOnce() override; +}; + +extern i2cButtonThread *i2cButton; +#endif diff --git a/src/main.cpp b/src/main.cpp index 401ea7592..d7e866a2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -385,7 +385,6 @@ void setup() io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.pinMode(EXPANDS_SD_PULLEN, INPUT); #endif - concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0); @@ -544,6 +543,12 @@ void setup() #endif #endif +#if defined(M5STACK_UNITC6L) + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, 1); + c6l_init(); +#endif + #ifdef PIN_LCD_RESET // FIXME - move this someplace better, LCD is at address 0x3F pinMode(PIN_LCD_RESET, OUTPUT); @@ -877,7 +882,8 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && @@ -1140,7 +1146,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index c8eba1b2e..6473722d7 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e9165e57c..2fc0bf4a6 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1432,10 +1432,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node; if (node) { if (node->is_favorite) { +#if defined(M5STACK_UNITC6L) + snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name); + } else { + snprintf(entryText, sizeof(entryText), "%s", node->user.short_name); + } +#else snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name); } else { snprintf(entryText, sizeof(entryText), "%s", node->user.long_name); } +#endif } } } @@ -1606,7 +1613,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int yOffset = y + 10; #else display->setFont(FONT_MEDIUM); +#if defined(M5STACK_UNITC6L) + int yOffset = y; +#else int yOffset = y + 10; +#endif #endif // --- Delivery Status Message --- @@ -1631,13 +1642,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } display->drawString(display->getWidth() / 2 + x, yOffset, buffer); +#if defined(M5STACK_UNITC6L) + yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding +#else yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding - +#endif #ifndef USE_EINK // --- SNR + RSSI Compact Line --- if (this->ack) { display->setFont(FONT_SMALL); +#if defined(M5STACK_UNITC6L) + snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi); +#else snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi); +#endif display->drawString(display->getWidth() / 2 + x, yOffset, buffer); } #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index d4beb6824..757753d45 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -7,6 +7,7 @@ #include "input/RotaryEncoderInterruptImpl1.h" #include "input/SerialKeyboardImpl.h" #include "input/UpDownInterruptImpl1.h" +#include "input/i2cButton.h" #include "modules/SystemCommandsModule.h" #if HAS_TRACKBALL #include "input/TrackballInterruptImpl1.h" @@ -196,6 +197,9 @@ void setupModules() #endif cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); +#if defined(M5STACK_UNITC6L) + i2cButton = new i2cButtonThread("i2cButtonThread"); +#endif #ifdef INPUTBROKER_MATRIX_TYPE kbMatrixImpl = new KbMatrixImpl(); kbMatrixImpl->init(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index ee95168c3..0eb8e9bdd 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -11,6 +11,12 @@ #include #include +#ifdef NIMBLE_TWO +#include "NimBLEAdvertising.h" +#include "NimBLEExtAdvertising.h" +#include "PowerStatus.h" +#endif + NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; NimBLECharacteristic *logRadioCharacteristic; @@ -56,13 +62,18 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { PhoneAPI::onNowHasData(fromRadioNum); - LOG_DEBUG("BLE notify fromNum"); + uint8_t cc = bleServer->getConnectedCount(); + LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc); uint8_t val[4]; put_le32(val, fromRadioNum); fromNumCharacteristic->setValue(val, sizeof(val)); +#ifdef NIMBLE_TWO + fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE); +#else fromNumCharacteristic->notify(); +#endif } /// Check the current underlying physical link to see if the client is currently connected @@ -79,7 +90,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks { +#ifdef NIMBLE_TWO + virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) +#else virtual void onWrite(NimBLECharacteristic *pCharacteristic) + +#endif { auto val = pCharacteristic->getValue(); @@ -97,7 +113,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { +#ifdef NIMBLE_TWO + virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) +#else virtual void onRead(NimBLECharacteristic *pCharacteristic) +#endif { int tries = 0; bluetoothPhoneAPI->phoneWants = true; @@ -107,9 +127,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks tries++; } std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); - std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes, - bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes); - pCharacteristic->setValue(fromRadioByteString); + pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes); if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload bluetoothPhoneAPI->setIntervalFromNow(0); @@ -121,7 +139,17 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { +#ifdef NIMBLE_TWO + public: + NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; } + + private: + NimbleBluetooth *ble; + + virtual uint32_t onPassKeyDisplay() +#else virtual uint32_t onPassKeyRequest() +#endif { uint32_t passkey = config.bluetooth.fixed_pin; @@ -170,7 +198,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks return passkey; } +#ifdef NIMBLE_TWO + virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) +#else virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) +#endif { LOG_INFO("BLE authentication complete"); @@ -185,9 +217,20 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks } } +#ifdef NIMBLE_TWO + virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) + { + LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); + } + + virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) + { + LOG_INFO("BLE disconnect reason: %d", reason); +#else virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) { LOG_INFO("BLE disconnect"); +#endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -200,6 +243,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothPhoneAPI->numBytes = 0; bluetoothPhoneAPI->queue_size = 0; } +#ifdef NIMBLE_TWO + // Restart Advertising + ble->startAdvertising(); +#endif } }; @@ -251,7 +298,11 @@ int NimbleBluetooth::getRssi() if (bleServer && isConnected()) { auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID); uint16_t handle = service->getHandle(); +#ifdef NIMBLE_TWO + return NimBLEDevice::getClientByHandle(handle)->getRssi(); +#else return NimBLEDevice::getClientByID(handle)->getRssi(); +#endif } return 0; // FIXME figure out where to source this } @@ -273,8 +324,11 @@ void NimbleBluetooth::setup() NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); } bleServer = NimBLEDevice::createServer(); - +#ifdef NIMBLE_TWO + NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); +#else NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(); +#endif bleServer->setCallbacks(serverCallbacks, true); setupService(); startAdvertising(); @@ -318,8 +372,11 @@ void NimbleBluetooth::setupService() NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1); - +#ifdef NIMBLE_TWO + NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904(); +#else NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904); +#endif batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); batteryLevelDescriptor->setNamespace(1); batteryLevelDescriptor->setUnit(0x27ad); @@ -329,11 +386,40 @@ void NimbleBluetooth::setupService() void NimbleBluetooth::startAdvertising() { +#ifdef NIMBLE_TWO + NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + NimBLEExtAdvertisement legacyAdvertising; + + legacyAdvertising.setLegacyAdvertising(true); + legacyAdvertising.setScannable(true); + legacyAdvertising.setConnectable(true); + legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN); + if (powerStatus->getHasBattery() == 1) { + legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f)); + } + legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID)); + legacyAdvertising.setMinInterval(500); + legacyAdvertising.setMaxInterval(1000); + + NimBLEExtAdvertisement legacyScanResponse; + legacyScanResponse.setLegacyAdvertising(true); + legacyScanResponse.setConnectable(true); + legacyScanResponse.setName(getDeviceName()); + + if (!pAdvertising->setInstanceData(0, legacyAdvertising)) { + LOG_ERROR("BLE failed to set legacyAdvertising"); + } else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) { + LOG_ERROR("BLE failed to set legacyScanResponse"); + } else if (!pAdvertising->start(0, 0, 0)) { + LOG_ERROR("BLE failed to start legacyAdvertising"); + } +#else NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->addServiceUUID(MESH_SERVICE_UUID); pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service pAdvertising->start(0); +#endif } /// Given a level between 0-100, update the BLE attribute @@ -341,7 +427,11 @@ void updateBatteryLevel(uint8_t level) { if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { BatteryCharacteristic->setValue(&level, 1); +#ifdef NIMBLE_TWO + BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE); +#else BatteryCharacteristic->notify(); +#endif } } @@ -356,7 +446,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) if (!bleServer || !isConnected() || length > 512) { return; } +#ifdef NIMBLE_TWO + logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE); +#else logRadioCharacteristic->notify(logMessage, length, true); +#endif } void clearNVS() @@ -366,4 +460,4 @@ void clearNVS() ESP.restart(); #endif } -#endif +#endif \ No newline at end of file diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 45602e088..899355b4d 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -12,10 +12,15 @@ class NimbleBluetooth : BluetoothApi bool isConnected(); int getRssi(); void sendLog(const uint8_t *logMessage, size_t length); +#if defined(NIMBLE_TWO) + void startAdvertising(); +#endif private: void setupService(); +#if !defined(NIMBLE_TWO) void startAdvertising(); +#endif }; void setBluetoothEnable(bool enable); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index cb0f0dab3..22ce6487f 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -194,6 +194,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER +#elif defined(M5STACK_UNITC6L) +#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #endif // ----------------------------------------------------------------------------- diff --git a/variants/esp32c6/m5stack_unitc6l/pins_arduino.h b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h new file mode 100644 index 000000000..5b169a2d4 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h @@ -0,0 +1,28 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0048 + +static const uint8_t TX = 16; +static const uint8_t RX = 17; + +static const uint8_t SDA = 10; +static const uint8_t SCL = 8; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 22; +static const uint8_t SCK = 20; +static const uint8_t MOSI = 21; +static const uint8_t SS = 6; + +// #define SPI_MOSI (11) +// #define SPI_SCK (14) +// #define SPI_MISO (2) +// #define SPI_CS (13) + +// #define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini new file mode 100644 index 000000000..da1c70c0a --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -0,0 +1,35 @@ +[env:m5stack-unitc6l] +extends = esp32c6_base +board = esp32-c6-devkitc-1 +;OpenOCD flash method +;upload_protocol = esp-builtin +;Normal method +upload_protocol = esptool +;upload_port = /dev/ttyACM2 +build_unflags = + -D HAS_BLUETOOTH + -D MESHTASTIC_EXCLUDE_BLUETOOTH + -D HAS_WIFI +lib_deps = + ${esp32c6_base.lib_deps} + adafruit/Adafruit NeoPixel@^1.12.3 + h2zero/NimBLE-Arduino@^2.3.6 +build_flags = + ${esp32c6_base.build_flags} + -D M5STACK_UNITC6L + -I variants/esp32c6/m5stack_unitc6l + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 + -D HAS_BLUETOOTH=1 + -D MESHTASTIC_EXCLUDE_WEBSERVER + -D MESHTASTIC_EXCLUDE_MQTT + -DCONFIG_BT_NIMBLE_EXT_ADV=1 + -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + -D NIMBLE_TWO +monitor_speed=115200 +lib_ignore = + NonBlockingRTTTL + libpax +build_src_filter = + ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> \ No newline at end of file diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp new file mode 100644 index 000000000..8e26b4ab7 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp @@ -0,0 +1,74 @@ +#include "driver/gpio.h" +#include +#include +// I2C device addr +#define PI4IO_M_ADDR 0x43 + +// PI4IO registers +#define PI4IO_REG_CHIP_RESET 0x01 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 +#define PI4IO_REG_IN_DEF_STA 0x09 +#define PI4IO_REG_PULL_EN 0x0B +#define PI4IO_REG_PULL_SEL 0x0D +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_INT_MASK 0x11 +#define PI4IO_REG_IRQ_STA 0x13 +// PI4IO + +#define setbit(x, y) x |= (0x01 << y) +#define clrbit(x, y) x &= ~(0x01 << y) +#define reversebit(x, y) x ^= (0x01 << y) +#define getbit(x, y) ((x) >> (y)&0x01) + +void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value) +{ + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom(addr, 1); + *value = Wire.read(); +} + +/*******************************************************************/ +void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value) +{ + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.write(value); + Wire.endTransmission(); +} +/*******************************************************************/ +void c6l_init() +{ + // P7 LoRa Reset + // P6 RF Switch + // P5 LNA Enable + + printf("pi4io_init\n"); + uint8_t in_data; + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, 0xFF); + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data); + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志 + + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data); + setbit(in_data, 6); // HIGH + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data); +} diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h new file mode 100644 index 000000000..d973aa281 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -0,0 +1,52 @@ +void c6l_init(); + +#define HAS_GPS 1 +#define GPS_RX_PIN 4 +#define GPS_TX_PIN 5 + +#define I2C_SDA 10 +#define I2C_SCL 8 + +#define PIN_BUZZER 11 + +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 2 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting + +// #define BUTTON_PIN 9 +#define BUTTON_EXTENDER + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// WaveShare Core1262-868M OK +// https://www.waveshare.com/wiki/Core1262-868M +#define USE_SX1262 + +#define LORA_MISO 22 +#define LORA_SCK 20 +#define LORA_MOSI 21 +#define LORA_CS 23 +#define LORA_RESET RADIOLIB_NC +#define LORA_DIO1 7 +#define LORA_BUSY 19 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +#define USE_SPISSD1306 +#ifdef USE_SPISSD1306 +#define SSD1306_NSS 6 // CS +#define SSD1306_RS 18 // DC +#define SSD1306_RESET 15 +// #define OLED_DG 1 +#endif +#define SCREEN_TRANSITION_FRAMERATE 10 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness