From 8aa0f0388e9fe1c0dc82497108fb9a67726d2e47 Mon Sep 17 00:00:00 2001 From: pelgraine Date: Sun, 5 Apr 2026 21:14:52 +1000 Subject: [PATCH 1/2] meck wifi remote repeater heltec v4 --- data/remote/mqtt.cfg | 5 + data/remote/wifi.cfg | 2 + examples/simple_repeater/main.cpp | 28 ++- examples/simple_repeater/wifimqtt.cpp | 56 +++-- examples/simple_repeater/wifimqtt.h | 3 + variants/heltec_v4/HeltecV4Board.cpp | 103 ++++++++ variants/heltec_v4/HeltecV4Board.h | 23 ++ variants/heltec_v4/README.md | 61 +++++ variants/heltec_v4/pins_arduino.h | 67 ++++++ variants/heltec_v4/platformio.ini | 333 ++++++++++++++++++++++++++ variants/heltec_v4/target.cpp | 61 +++++ variants/heltec_v4/target.h | 35 +++ 12 files changed, 755 insertions(+), 22 deletions(-) create mode 100644 data/remote/mqtt.cfg create mode 100644 data/remote/wifi.cfg create mode 100644 variants/heltec_v4/HeltecV4Board.cpp create mode 100644 variants/heltec_v4/HeltecV4Board.h create mode 100644 variants/heltec_v4/README.md create mode 100644 variants/heltec_v4/pins_arduino.h create mode 100644 variants/heltec_v4/platformio.ini create mode 100644 variants/heltec_v4/target.cpp create mode 100644 variants/heltec_v4/target.h diff --git a/data/remote/mqtt.cfg b/data/remote/mqtt.cfg new file mode 100644 index 00000000..b8d63736 --- /dev/null +++ b/data/remote/mqtt.cfg @@ -0,0 +1,5 @@ +6818ce5f77dd45bb90facf753ba81d81.s1.eu.hivemq.cloud +8883 +meckremote +yourpassword +heltec-wifi-1 \ No newline at end of file diff --git a/data/remote/wifi.cfg b/data/remote/wifi.cfg new file mode 100644 index 00000000..8a441ab4 --- /dev/null +++ b/data/remote/wifi.cfg @@ -0,0 +1,2 @@ +SSID +Password \ No newline at end of file diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1bbe7cb4..fcd70605 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -9,7 +9,9 @@ #endif #ifdef MECK_WIFI_REMOTE +#if defined(HAS_SDCARD) || defined(SDCARD_CS) #include +#endif #include "WiFiMQTT.h" #endif @@ -33,7 +35,7 @@ static char command[160]; unsigned long lastActive = 0; // mark last active time unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot -#if defined(HAS_4G_MODEM) || defined(MECK_WIFI_REMOTE) +#if (defined(HAS_4G_MODEM) || defined(MECK_WIFI_REMOTE)) && (defined(HAS_SDCARD) || defined(SDCARD_CS)) static bool sdCardReady = false; #endif @@ -98,11 +100,12 @@ void setup() { the_mesh.begin(fs); // --------------------------------------------------------------------------- - // SD card init — needed for MQTT config (/remote/mqtt.cfg, /remote/wifi.cfg) + // SD card init — needed for MQTT config on devices with SD slots. // T-Deck Pro: SD shares display SPI bus (HSPI via displaySpi) // T5S3: SD shares LoRa SPI bus (SCK=14, MOSI=13, MISO=21) + // Heltec V4 and others without SD: config lives in SPIFFS (already init'd) // --------------------------------------------------------------------------- -#if defined(HAS_4G_MODEM) || defined(MECK_WIFI_REMOTE) +#if (defined(HAS_4G_MODEM) || defined(MECK_WIFI_REMOTE)) && (defined(HAS_SDCARD) || defined(SDCARD_CS)) { // Deselect all SPI devices before SD init to prevent bus contention #ifdef SDCARD_CS @@ -135,6 +138,7 @@ void setup() { } Serial.printf("SD card: %s\n", sdCardReady ? "ready" : "FAILED"); } +#endif // Start MQTT backhaul #ifdef HAS_4G_MODEM @@ -147,16 +151,20 @@ void setup() { #endif #ifdef MECK_WIFI_REMOTE + #if defined(HAS_SDCARD) || defined(SDCARD_CS) if (sdCardReady) { wifiMQTT.begin(); Serial.println("WiFi MQTT starting..."); } else { Serial.println("WiFi MQTT skipped — no SD card for config"); } + #else + // No SD card slot — config lives in SPIFFS (already initialized above) + wifiMQTT.begin(); + Serial.println("WiFi MQTT starting (SPIFFS config)..."); + #endif #endif -#endif // HAS_4G_MODEM || MECK_WIFI_REMOTE - #ifdef DISPLAY_CLASS ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif @@ -220,8 +228,13 @@ void loop() { memset(&td, 0, sizeof(td)); td.uptime_secs = millis() / 1000; td.battery_mv = board.getBattMilliVolts(); +#ifdef HAS_BQ27220 td.battery_pct = board.getBatteryPercent(); td.temperature = board.getBattTemperature(); +#else + td.battery_pct = 0; + td.temperature = 0; +#endif td.csq = cellularMQTT.getCSQ(); td.freq = p->freq; td.bw = p->bw; @@ -270,8 +283,13 @@ void loop() { memset(&td, 0, sizeof(td)); td.uptime_secs = millis() / 1000; td.battery_mv = board.getBattMilliVolts(); +#ifdef HAS_BQ27220 td.battery_pct = board.getBatteryPercent(); td.temperature = board.getBattTemperature(); +#else + td.battery_pct = 0; + td.temperature = 0; +#endif td.rssi = wifiMQTT.getRSSI(); td.freq = p->freq; td.bw = p->bw; diff --git a/examples/simple_repeater/wifimqtt.cpp b/examples/simple_repeater/wifimqtt.cpp index 8ee657e2..4b02386c 100644 --- a/examples/simple_repeater/wifimqtt.cpp +++ b/examples/simple_repeater/wifimqtt.cpp @@ -171,10 +171,21 @@ const char* WiFiMQTT::stateString() const { bool WiFiMQTT::loadConfig(WiFiMQTTConfig& cfg) { memset(&cfg, 0, sizeof(cfg)); + // Determine filesystem: SD if available, otherwise SPIFFS + // Heltec V4 and other headless boards have no SD slot — config lives in SPIFFS. + // Upload config files via: pio run -t uploadfs (with data/ folder) +#if defined(HAS_SDCARD) || defined(SDCARD_CS) + fs::FS& configFS = SD; + Serial.println("[WiFi] Config source: SD card"); +#else + fs::FS& configFS = SPIFFS; + Serial.println("[WiFi] Config source: SPIFFS"); +#endif + // WiFi config: read SSID/password pairs - File wf = SD.open(WIFI_CONFIG_FILE, FILE_READ); + File wf = configFS.open(WIFI_CONFIG_FILE, FILE_READ); if (!wf) { - Serial.println("[WiFi] No /remote/wifi.cfg"); + Serial.printf("[WiFi] No %s\n", WIFI_CONFIG_FILE); return false; } @@ -195,9 +206,9 @@ bool WiFiMQTT::loadConfig(WiFiMQTTConfig& cfg) { } // MQTT config: /remote/mqtt.cfg (same format as cellular) - File mf = SD.open(MQTT_CONFIG_FILE, FILE_READ); + File mf = configFS.open(MQTT_CONFIG_FILE, FILE_READ); if (!mf) { - Serial.println("[WiFi] No /remote/mqtt.cfg"); + Serial.printf("[WiFi] No %s\n", MQTT_CONFIG_FILE); return false; } String line; @@ -270,6 +281,7 @@ bool WiFiMQTT::connectWiFi() { } time_t now = time(nullptr); if (now > 1700000000) { + extern AutoDiscoverRTCClock rtc_clock; rtc_clock.setCurrentTime((uint32_t)now); Serial.printf(" OK (%lu)\n", (unsigned long)now); } else { @@ -388,36 +400,37 @@ void WiFiMQTT::performOTA() { Serial.printf("[OTA] URL: %s\n", _otaUrl); - // Disconnect MQTT cleanly — we need TLS resources for HTTP - _mqttClient.disconnect(); - - // Use a separate TLS client — don't reuse the MQTT one - WiFiClientSecure otaClient; - otaClient.setInsecure(); + _mqttClient.publish(_topicRsp, "OTA: Starting download..."); + _mqttClient.loop(); HTTPClient http; http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.setTimeout(180000); - if (!http.begin(otaClient, _otaUrl)) { + if (!http.begin(_wifiClient, _otaUrl)) { Serial.println("[OTA] HTTP begin failed"); - _state = WiFiMQTTState::MQTT_CONNECTING; + _mqttClient.publish(_topicRsp, "OTA: HTTP begin failed"); + _state = WiFiMQTTState::CONNECTED; return; } int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { Serial.printf("[OTA] HTTP error: %d\n", httpCode); + char msg[60]; + snprintf(msg, sizeof(msg), "OTA: HTTP error %d", httpCode); + _mqttClient.publish(_topicRsp, msg); http.end(); - _state = WiFiMQTTState::MQTT_CONNECTING; + _state = WiFiMQTTState::CONNECTED; return; } int fileSize = http.getSize(); if (fileSize <= 0) { Serial.println("[OTA] Unknown content length"); + _mqttClient.publish(_topicRsp, "OTA: Unknown file size"); http.end(); - _state = WiFiMQTTState::MQTT_CONNECTING; + _state = WiFiMQTTState::CONNECTED; return; } @@ -425,8 +438,9 @@ void WiFiMQTT::performOTA() { if (!Update.begin(fileSize)) { Serial.printf("[OTA] Update.begin failed: %s\n", Update.errorString()); + _mqttClient.publish(_topicRsp, "OTA: Flash init failed"); http.end(); - _state = WiFiMQTTState::MQTT_CONNECTING; + _state = WiFiMQTTState::CONNECTED; return; } @@ -458,6 +472,10 @@ void WiFiMQTT::performOTA() { int pct = (offset * 100) / fileSize; if (pct / 10 != lastPct / 10) { Serial.printf("[OTA] Progress: %d%% (%d/%d)\n", pct, offset, fileSize); + char msg[60]; + snprintf(msg, sizeof(msg), "OTA: Flashing %d%%", pct); + _mqttClient.publish(_topicRsp, msg); + _mqttClient.loop(); lastPct = pct; } @@ -469,17 +487,21 @@ void WiFiMQTT::performOTA() { if (offset < fileSize) { Serial.printf("[OTA] Incomplete: %d of %d\n", offset, fileSize); Update.abort(); - _state = WiFiMQTTState::MQTT_CONNECTING; + _mqttClient.publish(_topicRsp, "OTA: Download incomplete"); + _state = WiFiMQTTState::CONNECTED; return; } if (!Update.end(true)) { Serial.printf("[OTA] Update.end failed: %s\n", Update.errorString()); - _state = WiFiMQTTState::MQTT_CONNECTING; + _mqttClient.publish(_topicRsp, "OTA: Verification failed"); + _state = WiFiMQTTState::CONNECTED; return; } Serial.println("[OTA] SUCCESS — rebooting in 3 seconds"); + _mqttClient.publish(_topicRsp, "OTA: Success! Rebooting..."); + _mqttClient.loop(); delay(3000); ESP.restart(); } diff --git a/examples/simple_repeater/wifimqtt.h b/examples/simple_repeater/wifimqtt.h index 4dc3b5a1..ae3bf055 100644 --- a/examples/simple_repeater/wifimqtt.h +++ b/examples/simple_repeater/wifimqtt.h @@ -21,7 +21,10 @@ #include #include #include +#if defined(HAS_SDCARD) || defined(SDCARD_CS) #include +#endif +#include // --------------------------------------------------------------------------- // Configuration diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp new file mode 100644 index 00000000..8186f2d4 --- /dev/null +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -0,0 +1,103 @@ +#include "HeltecV4Board.h" + +void HeltecV4Board::begin() { + ESP32Board::begin(); + + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); + + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN,LOW); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } + + periph_power.begin(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecV4Board::onBeforeTransmit(void) { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + digitalWrite(P_LORA_PA_TX_EN,HIGH); + } + + void HeltecV4Board::onAfterTransmit(void) { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + digitalWrite(P_LORA_PA_TX_EN,LOW); + } + + void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecV4Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t HeltecV4Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecV4Board::getManufacturerName() const { + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif + } diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h new file mode 100644 index 00000000..745e8d8f --- /dev/null +++ b/variants/heltec_v4/HeltecV4Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +class HeltecV4Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecV4Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit(void) override; + void onAfterTransmit(void) override; + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_v4/README.md b/variants/heltec_v4/README.md new file mode 100644 index 00000000..09d6f6df --- /dev/null +++ b/variants/heltec_v4/README.md @@ -0,0 +1,61 @@ +# Heltec V4 WiFi Remote Repeater — Setup Guide + +## Variant Files + +Copy the following into `variants/heltec_v4/`: +- `HeltecV4Board.h`, `HeltecV4Board.cpp` +- `target.h`, `target.cpp` +- `pins_arduino.h` + +Copy `heltec_v4.json` into `boards/` + +## Config Files (SPIFFS) + +The Heltec V4 has no SD card slot — config lives in SPIFFS. + +Create a `data/remote/` folder in your project root: + +``` +data/ + remote/ + wifi.cfg + mqtt.cfg +``` + +### data/remote/wifi.cfg +``` +YourSSID +YourPassword +BackupSSID +BackupPassword +``` + +### data/remote/mqtt.cfg +``` +6818ce5f77dd45bb90facf753ba81d81.s1.eu.hivemq.cloud +8883 +meckremote +yourpassword +heltec-wifi-1 +``` + +### Upload config to SPIFFS +```bash +pio run -e meck_wifi_repeater_heltec_v4 -t uploadfs +``` + +This uploads the `data/` folder contents to SPIFFS on the device. + +### Flash firmware +```bash +pio run -e meck_wifi_repeater_heltec_v4 -t upload +``` + +## Notes + +- The OLED display shows basic repeater status (same as stock repeater) +- WiFi MQTT and Mycelium dashboard work identically to T-Deck Pro builds +- OTA firmware updates work over WiFi via the Mycelium dashboard +- Config changes require re-uploading SPIFFS (`-t uploadfs`) +- The same `main.cpp`, `WiFiMQTT.h/cpp`, and `MyMesh.cpp` are shared + with T-Deck Pro and T5S3 builds — no Heltec-specific source changes diff --git a/variants/heltec_v4/pins_arduino.h b/variants/heltec_v4/pins_arduino.h new file mode 100644 index 00000000..a8b9f291 --- /dev/null +++ b/variants/heltec_v4/pins_arduino.h @@ -0,0 +1,67 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 3; +static const uint8_t SCL = 4; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 35; +static const uint8_t RST_OLED = 21; +static const uint8_t SCL_OLED = 18; +static const uint8_t SDA_OLED = 17; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini new file mode 100644 index 00000000..5acc3f62 --- /dev/null +++ b/variants/heltec_v4/platformio.ini @@ -0,0 +1,333 @@ +[Heltec_lora32_v4] +extends = esp32_base +board = heltec_v4 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_v4 + -D HELTEC_LORA_V4 + -D USE_SX1262 + -D ESP32_CPU_FREQ=80 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=35 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - Power on GC1109 + -D P_LORA_PA_EN=2 ; PA CSD - Enable GC1109 + -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=36 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX + -D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 ; In some cases, commenting this out will improve RX + -D PIN_GPS_RX=38 + -D PIN_GPS_TX=39 + -D PIN_GPS_RESET=42 + -D PIN_GPS_RESET_ACTIVE=LOW + -D PIN_GPS_EN=34 + -D PIN_GPS_EN_ACTIVE=LOW + -D ENV_INCLUDE_GPS=1 + -D PIN_ADC_CTRL=37 + -D PIN_VBAT_READ=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_v4> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[heltec_v4_oled] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D PIN_OLED_RESET=21 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_v4_repeater_bridge_espnow] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_room_server] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_companion_radio_usb] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_companion_radio_ble] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_companion_radio_wifi] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + + +; --------------------------------------------------------------------------- +; Heltec V4 WiFi Remote Repeater — WiFi MQTT backhaul, remote management +; No SD card — config files stored in SPIFFS. +; Upload config: create data/remote/ folder with wifi.cfg and mqtt.cfg, +; then run: pio run -e meck_wifi_repeater_heltec_v4 -t uploadfs +; OLED display shows status (optional — works headless too) +; Flash: pio run -e meck_wifi_repeater_heltec_v4 -t upload +; --------------------------------------------------------------------------- +[env:meck_wifi_repeater_heltec_v4] +extends = heltec_v4_oled +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + - + + + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${heltec_v4_oled.build_flags} + -D FIRMWARE_VERSION='"Meck HV4 WiFi Rptr v0.1"' + -D FIRMWARE_BUILD_DATE='"5 Apr 2026"' + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Repeater"' + -D ADMIN_PASSWORD='"password"' + -D MECK_WIFI_REMOTE + -D MECK_REMOTE_REPEATER=1 + -D DISABLE_WIFI_OTA=1 + -D MAX_NEIGHBOURS=50 + -D RADIOLIB_EXCLUDE_CC1101=1 + -D RADIOLIB_EXCLUDE_NRF24=1 + -D RADIOLIB_EXCLUDE_RF69=1 + -D RADIOLIB_EXCLUDE_SX1231=1 + -D RADIOLIB_EXCLUDE_SX1233=1 + -D RADIOLIB_EXCLUDE_SI443X=1 + -D RADIOLIB_EXCLUDE_RFM2X=1 + -D RADIOLIB_EXCLUDE_SX127X=1 + -D RADIOLIB_EXCLUDE_SX1272=1 + -D RADIOLIB_EXCLUDE_SX1278=1 + -D RADIOLIB_EXCLUDE_STM32WLX=1 + -D RADIOLIB_EXCLUDE_LR11X0=1 + -D RADIOLIB_EXCLUDE_LLCC68=1 + -D RADIOLIB_EXCLUDE_SX128X=1 + -D RADIOLIB_EXCLUDE_AFSK=1 + -D RADIOLIB_EXCLUDE_AX25=1 + -D RADIOLIB_EXCLUDE_HELLSCHREIBER=1 + -D RADIOLIB_EXCLUDE_MORSE=1 + -D RADIOLIB_EXCLUDE_RTTY=1 + -D RADIOLIB_EXCLUDE_SSTV=1 + -D RADIOLIB_EXCLUDE_APRS=1 + -D RADIOLIB_EXCLUDE_LORAWAN=1 + -D RADIOLIB_EXCLUDE_PAGER=1 + -D RADIOLIB_EXCLUDE_FSK4=1 + -D RADIOLIB_EXCLUDE_BELL=1 +lib_deps = + ${heltec_v4_oled.lib_deps} + knolleary/PubSubClient@^2.8 +lib_ignore = + ESP32 BLE Arduino + AsyncTCP + RPAsyncTCP + ESPAsyncWebServer + AsyncElegantOTA + ESP32-audioI2S + esp32_codec2_arduino +board_build.partitions = default_16MB.csv +board_build.filesystem = spiffs + +; --------------------------------------------------------------------------- +; Heltec V4 WiFi Remote Repeater — HEADLESS (no display) +; --------------------------------------------------------------------------- +[env:meck_wifi_repeater_heltec_v4_headless] +extends = Heltec_lora32_v4 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + + - + +<../examples/simple_repeater/*.cpp> +build_flags = + ${Heltec_lora32_v4.build_flags} + -D FIRMWARE_VERSION='"Meck HV4 WiFi Rptr v0.1"' + -D FIRMWARE_BUILD_DATE='"5 Apr 2026"' + -D ADVERT_NAME='"Heltec Repeater"' + -D ADMIN_PASSWORD='"password"' + -D MECK_WIFI_REMOTE + -D MECK_REMOTE_REPEATER=1 + -D DISABLE_WIFI_OTA=1 + -D MAX_NEIGHBOURS=50 + -D RADIOLIB_EXCLUDE_CC1101=1 + -D RADIOLIB_EXCLUDE_NRF24=1 + -D RADIOLIB_EXCLUDE_RF69=1 + -D RADIOLIB_EXCLUDE_SX1231=1 + -D RADIOLIB_EXCLUDE_SX1233=1 + -D RADIOLIB_EXCLUDE_SI443X=1 + -D RADIOLIB_EXCLUDE_RFM2X=1 + -D RADIOLIB_EXCLUDE_SX127X=1 + -D RADIOLIB_EXCLUDE_SX1272=1 + -D RADIOLIB_EXCLUDE_SX1278=1 + -D RADIOLIB_EXCLUDE_STM32WLX=1 + -D RADIOLIB_EXCLUDE_LR11X0=1 + -D RADIOLIB_EXCLUDE_LLCC68=1 + -D RADIOLIB_EXCLUDE_SX128X=1 + -D RADIOLIB_EXCLUDE_AFSK=1 + -D RADIOLIB_EXCLUDE_AX25=1 + -D RADIOLIB_EXCLUDE_HELLSCHREIBER=1 + -D RADIOLIB_EXCLUDE_MORSE=1 + -D RADIOLIB_EXCLUDE_RTTY=1 + -D RADIOLIB_EXCLUDE_SSTV=1 + -D RADIOLIB_EXCLUDE_APRS=1 + -D RADIOLIB_EXCLUDE_LORAWAN=1 + -D RADIOLIB_EXCLUDE_PAGER=1 + -D RADIOLIB_EXCLUDE_FSK4=1 + -D RADIOLIB_EXCLUDE_BELL=1 +lib_deps = + ${Heltec_lora32_v4.lib_deps} + knolleary/PubSubClient@^2.8 +lib_ignore = + ESP32 BLE Arduino + AsyncTCP + RPAsyncTCP + ESPAsyncWebServer + AsyncElegantOTA + ESP32-audioI2S + esp32_codec2_arduino +board_build.partitions = default_16MB.csv +board_build.filesystem = spiffs \ No newline at end of file diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp new file mode 100644 index 00000000..d9f53596 --- /dev/null +++ b/variants/heltec_v4/target.cpp @@ -0,0 +1,61 @@ +#include +#include "target.h" + +HeltecV4Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display(NULL); + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(int8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h new file mode 100644 index 00000000..47d493cb --- /dev/null +++ b/variants/heltec_v4/target.h @@ -0,0 +1,35 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#ifdef HELTEC_LORA_V4_OLED + #include +#elif defined(HELTEC_LORA_V4_TFT) + #include +#endif + #include +#endif + +extern HeltecV4Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); + From 595f0073f97d00a87b868445b1094f8c6d4c10de Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:04:36 +1000 Subject: [PATCH 2/2] =?UTF-8?q?TDeckBoard.cpp=20=E2=80=94=20both=20*=203?= =?UTF-8?q?=20/=202=20thresholds=20changed=20to=20>=20designCapacity=5FmAh?= =?UTF-8?q?,=20so=20FCC=3D3000=20with=20DC=3D2500=20now=20triggers=20the?= =?UTF-8?q?=20Qmax=20+=20stored=20FCC=20correction.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SerialBLEInterface.cpp — added esp_bt.h include and three esp_ble_tx_power_set calls at +9 dBm after BLEDevice::init(), covering default, advertising, and scan power types. MyMesh.h — FIRMWARE_VER_CODE bumped from 10 → 11. MyMesh.cpp — The RESP_CODE_DEVICE_INFO frame construction now: Byte 2: sends 0xFF (sentinel) when MAX_CONTACTS > 510, otherwise the normal MAX_CONTACTS / 2. Older apps interpret 0xFF as 510 contacts — completely harmless. Bytes 80-81 (new, appended after the version string): uint16_t little-endian with the true MAX_CONTACTS value. Apps that understand v11+ read it here. Apps < v11 ignore trailing bytes — the BLE/serial frame protocol is length-delimited, so extra bytes at the tail are safe. platformio.ini — Both BLE builds (meck_audio_ble, meck_4g_ble) bumped from 510 → 2000. mymesh.cpp: writeContactRespFrame return type change (return _serial->writeFrame() result) checkSerialInterface() batch-fill loop. --- examples/companion_radio/MyMesh.cpp | 35 ++- examples/companion_radio/MyMesh.h | 8 +- src/helpers/esp32/SerialBLEInterface.cpp | 22 +- src/helpers/esp32/SerialBLEInterface.h | 4 +- src/helpers/esp32/TBeamBoard.cpp | 350 ----------------------- src/helpers/esp32/TBeamBoard.h | 166 ----------- variants/lilygo_tdeck_pro/TDeckBoard.cpp | 4 +- variants/lilygo_tdeck_pro/platformio.ini | 23 +- 8 files changed, 65 insertions(+), 547 deletions(-) delete mode 100644 src/helpers/esp32/TBeamBoard.cpp delete mode 100644 src/helpers/esp32/TBeamBoard.h diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index d85b5a7a..66e8dbe2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -166,7 +166,7 @@ void MyMesh::writeDisabledFrame() { _serial->writeFrame(buf, 1); } -void MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) { +size_t MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) { int i = 0; out_frame[i++] = code; memcpy(&out_frame[i], contact.id.pub_key, PUB_KEY_SIZE); @@ -186,7 +186,7 @@ void MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) { i += 4; memcpy(&out_frame[i], &contact.lastmod, 4); i += 4; - _serial->writeFrame(out_frame, i); + return _serial->writeFrame(out_frame, i); } void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len) { @@ -3142,20 +3142,29 @@ void MyMesh::checkSerialInterface() { } else if (_iter_started // check if our ContactsIterator is 'running' && !_serial->isWriteBusy() // don't spam the Serial Interface too quickly! ) { + // Batch-fill: queue multiple contacts per loop iteration so the BLE + // send queue stays saturated during sync. writeFrame() returns 0 + // when the queue is full, which naturally throttles us. ContactInfo contact; - if (_iter.hasNext(this, contact)) { - if (contact.lastmod > _iter_filter_since) { // apply the 'since' filter - writeContactRespFrame(RESP_CODE_CONTACT, contact); - if (contact.lastmod > _most_recent_lastmod) { - _most_recent_lastmod = contact.lastmod; // save for the RESP_CODE_END_OF_CONTACTS frame + bool done = false; + int queued = 0; + while (!done && queued < 8) { // up to 8 per iteration to avoid starving loop() + if (_iter.hasNext(this, contact)) { + if (contact.lastmod > _iter_filter_since) { // apply the 'since' filter + if (writeContactRespFrame(RESP_CODE_CONTACT, contact) == 0) break; // queue full + queued++; + if (contact.lastmod > _most_recent_lastmod) { + _most_recent_lastmod = contact.lastmod; + } } + } else { // EOF + out_frame[0] = RESP_CODE_END_OF_CONTACTS; + memcpy(&out_frame[1], &_most_recent_lastmod, + 4); // include the most recent lastmod, so app can update their 'since' + _serial->writeFrame(out_frame, 5); + _iter_started = false; + done = true; } - } else { // EOF - out_frame[0] = RESP_CODE_END_OF_CONTACTS; - memcpy(&out_frame[1], &_most_recent_lastmod, - 4); // include the most recent lastmod, so app can update their 'since' - _serial->writeFrame(out_frame, 5); - _iter_started = false; } //} else if (!_serial->isWriteBusy()) { // checkConnections(); // TODO - deprecate the 'Connections' stuff diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 192907ae..50a53211 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,14 +5,14 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 10 +#define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "31 March 2026" +#define FIRMWARE_BUILD_DATE "7 April 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "Meck v1.6" +#define FIRMWARE_VERSION "Meck v1.6.1" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -243,7 +243,7 @@ private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); void writeDisabledFrame(); - void writeContactRespFrame(uint8_t code, const ContactInfo &contact); + size_t writeContactRespFrame(uint8_t code, const ContactInfo &contact); void updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len); void addToOfflineQueue(const uint8_t frame[], int len); int getFromOfflineQueue(uint8_t frame[]); diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index e6f1bd71..22c44187 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -1,4 +1,6 @@ #include "SerialBLEInterface.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" // See the following for generating UUIDs: // https://www.uuidgenerator.net/ @@ -27,6 +29,11 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code BLEDevice::setSecurityCallbacks(this); BLEDevice::setMTU(MAX_FRAME_SIZE); + // Boost BLE TX power for improved range (+9 dBm, up from default +3 dBm) + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P9); + BLESecurity sec; sec.setStaticPIN(pin_code); sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND); @@ -77,6 +84,18 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) { if (cmpl.success) { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success"); deviceConnected = true; + + // Request fast connection interval (15ms) for faster contact sync. + // Phone may negotiate higher, but most modern phones accept 15ms. + // Units are 1.25ms, so 12 = 15ms, 16 = 20ms. + esp_ble_conn_update_params_t conn_params; + memcpy(conn_params.bda, _remote_bda, 6); + conn_params.min_int = 12; // 15ms (12 × 1.25ms) + conn_params.max_int = 16; // 20ms (16 × 1.25ms) + conn_params.latency = 0; // no skipped intervals + conn_params.timeout = 400; // 4 seconds supervision timeout + esp_ble_gap_update_conn_params(&conn_params); + BLE_DEBUG_PRINTLN(" - Requested fast connection interval (15-20ms)"); } else { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*"); @@ -94,6 +113,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer) { void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id)); last_conn_id = param->connect.conn_id; + memcpy(_remote_bda, param->connect.remote_bda, 6); } void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { @@ -185,7 +205,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { return 0; } -#define BLE_WRITE_MIN_INTERVAL 30 +#define BLE_WRITE_MIN_INTERVAL 15 bool SerialBLEInterface::isWriteBusy() const { return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index e7ca1a14..a453e328 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -14,6 +14,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE bool oldDeviceConnected; bool _isEnabled; uint16_t last_conn_id; + uint8_t _remote_bda[6]; // peer BDA, stored in onConnect for conn param updates uint32_t _pin_code; unsigned long _last_write; unsigned long adv_restart_time; @@ -23,7 +24,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE uint8_t buf[MAX_FRAME_SIZE]; }; - #define FRAME_QUEUE_SIZE 8 + #define FRAME_QUEUE_SIZE 16 int recv_queue_len; Frame recv_queue[FRAME_QUEUE_SIZE]; int send_queue_len; @@ -58,6 +59,7 @@ public: _isEnabled = false; _last_write = 0; last_conn_id = 0; + memset(_remote_bda, 0, 6); send_queue_len = recv_queue_len = 0; } diff --git a/src/helpers/esp32/TBeamBoard.cpp b/src/helpers/esp32/TBeamBoard.cpp deleted file mode 100644 index 5f708d71..00000000 --- a/src/helpers/esp32/TBeamBoard.cpp +++ /dev/null @@ -1,350 +0,0 @@ -#if defined(TBEAM_SUPREME_SX1262) || defined(TBEAM_SX1262) || defined(TBEAM_SX1276) - -#include -#include "TBeamBoard.h" -//#include - -uint32_t deviceOnline = 0x00; - -bool pmuInterrupt; -static void setPmuFlag() -{ - pmuInterrupt = true; -} - -void TBeamBoard::begin() { - - ESP32Board::begin(); - - power_init(); - - //Configure user button - pinMode(PIN_USER_BTN, INPUT); - - #ifndef TBEAM_SUPREME_SX1262 - digitalWrite(P_LORA_TX_LED, HIGH); //inverted pin for SX1276 - HIGH for off - #endif - - //radiotype_detect(); - - esp_reset_reason_t reason = esp_reset_reason(); - if (reason == ESP_RST_DEEPSLEEP) { - long wakeup_source = esp_sleep_get_ext1_wakeup_status(); - if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) - startup_reason = BD_STARTUP_RX_PACKET; - } - - rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); - rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); - } -} - -#ifdef MESH_DEBUG -void TBeamBoard::scanDevices(TwoWire *w) -{ - uint8_t err, addr; - int nDevices = 0; - uint32_t start = 0; - - Serial.println("Scanning I2C for Devices"); - for (addr = 1; addr < 127; addr++) { - start = millis(); - w->beginTransmission(addr); delay(2); - err = w->endTransmission(); - if (err == 0) { - nDevices++; - switch (addr) { - case 0x77: - case 0x76: - Serial.println("\tFound BME280 Sensor"); - deviceOnline |= BME280_ONLINE; - break; - case 0x34: - Serial.println("\tFound AXP192/AXP2101 PMU"); - deviceOnline |= POWERMANAGE_ONLINE; - break; - case 0x3C: - Serial.println("\tFound SSD1306/SH1106 display"); - deviceOnline |= DISPLAY_ONLINE; - break; - case 0x51: - Serial.println("\tFound PCF8563 RTC"); - deviceOnline |= PCF8563_ONLINE; - break; - case 0x1C: - Serial.println("\tFound QMC6310 MAG Sensor"); - deviceOnline |= QMC6310_ONLINE; - break; - default: - Serial.print("\tI2C device found at address 0x"); - if (addr < 16) { - Serial.print("0"); - } - Serial.print(addr, HEX); - Serial.println(" !"); - break; - } - - } else if (err == 4) { - Serial.print("Unknow error at address 0x"); - if (addr < 16) { - Serial.print("0"); - } - Serial.println(addr, HEX); - } - } - if (nDevices == 0) - Serial.println("No I2C devices found\n"); - - Serial.println("Scan for devices is complete."); - Serial.println("\n"); - - Serial.printf("GPS RX pin: %d", PIN_GPS_RX); - Serial.printf(" GPS TX pin: %d", PIN_GPS_TX); - Serial.println(); -} -void TBeamBoard::printPMU() -{ - Serial.print("isCharging:"); Serial.println(PMU->isCharging() ? "YES" : "NO"); - Serial.print("isDischarge:"); Serial.println(PMU->isDischarge() ? "YES" : "NO"); - Serial.print("isVbusIn:"); Serial.println(PMU->isVbusIn() ? "YES" : "NO"); - Serial.print("getBattVoltage:"); Serial.print(PMU->getBattVoltage()); Serial.println("mV"); - Serial.print("getVbusVoltage:"); Serial.print(PMU->getVbusVoltage()); Serial.println("mV"); - Serial.print("getSystemVoltage:"); Serial.print(PMU->getSystemVoltage()); Serial.println("mV"); - - // The battery percentage may be inaccurate at first use, the PMU will automatically - // learn the battery curve and will automatically calibrate the battery percentage - // after a charge and discharge cycle - if (PMU->isBatteryConnect()) { - Serial.print("getBatteryPercent:"); Serial.print(PMU->getBatteryPercent()); Serial.println("%"); - } - - Serial.println(); -} -#endif - -bool TBeamBoard::power_init() -{ - if (!PMU) { - #ifdef TBEAM_SUPREME_SX1262 - PMU = new XPowersAXP2101(PMU_WIRE_PORT, PIN_BOARD_SDA1, PIN_BOARD_SCL1, I2C_PMU_ADD); - #else - PMU = new XPowersAXP2101(PMU_WIRE_PORT, PIN_BOARD_SDA, PIN_BOARD_SCL, I2C_PMU_ADD); - #endif - if (!PMU->init()) { - MESH_DEBUG_PRINTLN("Warning: Failed to find AXP2101 power management"); - delete PMU; - PMU = NULL; - } else { - MESH_DEBUG_PRINTLN("AXP2101 PMU init succeeded, using AXP2101 PMU"); - } - } - if (!PMU) { - PMU = new XPowersAXP192(PMU_WIRE_PORT, PIN_BOARD_SDA, PIN_BOARD_SCL, I2C_PMU_ADD); - if (!PMU->init()) { - MESH_DEBUG_PRINTLN("Warning: Failed to find AXP192 power management"); - delete PMU; - PMU = NULL; - } else { - MESH_DEBUG_PRINTLN("AXP192 PMU init succeeded, using AXP192 PMU"); - } - } - - if (!PMU) { - return false; - } - - deviceOnline |= POWERMANAGE_ONLINE; - - PMU->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); - - // Set up PMU interrupts - pinMode(PIN_PMU_IRQ, INPUT_PULLUP); - attachInterrupt(PIN_PMU_IRQ, setPmuFlag, FALLING); - - if (PMU->getChipModel() == XPOWERS_AXP192) { - - PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300); //Set up LoRa power rail - PMU->enablePowerOutput(XPOWERS_LDO2); //Enable the LoRa power rail - - PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); //Set up OLED power rail - PMU->enablePowerOutput(XPOWERS_DCDC1); //Enable the OLED power rail - - PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300); //Set up GPS power rail - PMU->enablePowerOutput(XPOWERS_LDO3); //Enable the GPS power rail - - PMU->setProtectedChannel(XPOWERS_DCDC1); //Protect the OLED power rail - PMU->setProtectedChannel(XPOWERS_DCDC3); //Protect the ESP32 power rail - - PMU->disablePowerOutput(XPOWERS_DCDC2); //Disable unsused power rail DC2 - - PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ); //Disable PMU IRQ - - PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_450MA); //Set battery charging current - PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); //Set battery charge-stop voltage - } - else if(PMU->getChipModel() == XPOWERS_AXP2101){ - #ifdef TBEAM_SUPREME_SX1262 - //Set up the GPS power rail - PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); - PMU->enablePowerOutput(XPOWERS_ALDO4); - - //Set up the LoRa power rail - PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); - PMU->enablePowerOutput(XPOWERS_ALDO3); - - //Set up power rail for the M.2 interface - PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300); - PMU->enablePowerOutput(XPOWERS_DCDC3); - - if (ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()) { - MESH_DEBUG_PRINTLN("Power off and restart ALDO BLDO.."); - PMU->disablePowerOutput(XPOWERS_ALDO1); - PMU->disablePowerOutput(XPOWERS_ALDO2); - PMU->disablePowerOutput(XPOWERS_BLDO1); - delay(250); - } - - //Set up power rail for QMC6310U - PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); - PMU->enablePowerOutput(XPOWERS_ALDO2); - - //Set up power rail for BME280 and OLED - PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); - PMU->enablePowerOutput(XPOWERS_ALDO1); - - //Set up pwer rail for SD Card - PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); - PMU->enablePowerOutput(XPOWERS_BLDO1); - - //Set up power rail BLDO2 to headers - PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300); - PMU->enablePowerOutput(XPOWERS_BLDO2); - - //Set up power rail DCDC4 to headers - PMU->setPowerChannelVoltage(XPOWERS_DCDC4, XPOWERS_AXP2101_DCDC4_VOL2_MAX); - PMU->enablePowerOutput(XPOWERS_DCDC4); - - //Set up power rail DCDC5 to headers - PMU->setPowerChannelVoltage(XPOWERS_DCDC5, 3300); - PMU->enablePowerOutput(XPOWERS_DCDC5); - - //Disable unused power rails - PMU->disablePowerOutput(XPOWERS_DCDC2); - PMU->disablePowerOutput(XPOWERS_DLDO1); - PMU->disablePowerOutput(XPOWERS_DLDO2); - PMU->disablePowerOutput(XPOWERS_VBACKUP); - #else - //Turn off unused power rails - PMU->disablePowerOutput(XPOWERS_DCDC2); - PMU->disablePowerOutput(XPOWERS_DCDC3); - PMU->disablePowerOutput(XPOWERS_DCDC4); - PMU->disablePowerOutput(XPOWERS_DCDC5); - PMU->disablePowerOutput(XPOWERS_ALDO1); - PMU->disablePowerOutput(XPOWERS_ALDO4); - PMU->disablePowerOutput(XPOWERS_BLDO1); - PMU->disablePowerOutput(XPOWERS_BLDO2); - PMU->disablePowerOutput(XPOWERS_DLDO1); - PMU->disablePowerOutput(XPOWERS_DLDO2); - //PMU->disablePowerOutput(XPOWERS_CPULDO); - - PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300); //Set up GPS RTC power - PMU->enablePowerOutput(XPOWERS_VBACKUP); //Turn on GPS RTC power - - PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); //Set up LoRa power rail - PMU->enablePowerOutput(XPOWERS_ALDO2); //Enable LoRa power rail - - PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); //Set up GPS power rail - PMU->enablePowerOutput(XPOWERS_ALDO3); //Enable GPS power rail - - #endif - - PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); //Disable all PMU interrupts - - PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); //Set battery charging current to 500mA - PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); //Set battery charging cutoff voltage to 4.2V - - } - - PMU->clearIrqStatus(); //Clear interrupt flags - - PMU->disableTSPinMeasure(); //Disable TS detection, since it is not used - - //Enable voltage measurements - PMU->enableSystemVoltageMeasure(); - PMU->enableVbusVoltageMeasure(); - PMU->enableBattVoltageMeasure(); - -#ifdef MESH_DEBUG - scanDevices(&Wire); - printPMU(); -#endif - - // Set the power key off press time - PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); - return true; -} - -#pragma region "Debug code" -// void TBeamBoard::radiotype_detect(){ - -// static SPIClass spi; -// char chipTypeInfo; - -// #if defined(P_LORA_SCLK) -// spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); -// #endif - -// for(int i = 0; i -#include -#include "XPowersLib.h" -#include "helpers/ESP32Board.h" -#include - -class TBeamBoard : public ESP32Board { -XPowersLibInterface *PMU = NULL; -//PhysicalLayer * pl; -//RadioType * radio = NULL; -// int radioVersions = 2; - -enum { - POWERMANAGE_ONLINE = _BV(0), - DISPLAY_ONLINE = _BV(1), - RADIO_ONLINE = _BV(2), - GPS_ONLINE = _BV(3), - PSRAM_ONLINE = _BV(4), - SDCARD_ONLINE = _BV(5), - AXDL345_ONLINE = _BV(6), - BME280_ONLINE = _BV(7), - BMP280_ONLINE = _BV(8), - BME680_ONLINE = _BV(9), - QMC6310_ONLINE = _BV(10), - QMI8658_ONLINE = _BV(11), - PCF8563_ONLINE = _BV(12), - OSC32768_ONLINE = _BV(13), -}; - -bool power_init(); -//void radiotype_detect(); - -public: - -#ifdef MESH_DEBUG - void printPMU(); - void scanDevices(TwoWire *w); -#endif - void begin(); - - #ifndef TBEAM_SUPREME_SX1262 - void onBeforeTransmit() override{ - digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276 - } - void onAfterTransmit() override{ - digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off - invert pin for SX1276 - } - #endif - - void enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! -} - - uint16_t getBattMilliVolts(){ - return PMU->getBattVoltage(); - } - - const char* getManufacturerName() const{ - return "LilyGo T-Beam"; - } -}; - -#endif diff --git a/variants/lilygo_tdeck_pro/TDeckBoard.cpp b/variants/lilygo_tdeck_pro/TDeckBoard.cpp index c6351717..14c58210 100644 --- a/variants/lilygo_tdeck_pro/TDeckBoard.cpp +++ b/variants/lilygo_tdeck_pro/TDeckBoard.cpp @@ -199,7 +199,7 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { // Design Capacity correct, but check if Full Charge Capacity is sane. uint16_t fcc = bq27220_read16(BQ27220_REG_FULL_CAP); Serial.printf("BQ27220: Design Capacity already correct, FCC=%d mAh\n", fcc); - if (fcc >= designCapacity_mAh * 3 / 2) { + if (fcc > designCapacity_mAh) { // FCC is >=150% of design — stale from factory defaults (typically 3000 mAh). uint16_t designEnergy = (uint16_t)((uint32_t)designCapacity_mAh * 37 / 10); Serial.printf("BQ27220: FCC %d >> DC %d, checking Design Energy (target %d mWh)\n", @@ -344,7 +344,7 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { fcc = bq27220_read16(BQ27220_REG_FULL_CAP); Serial.printf("BQ27220: FCC after RESET: %d mAh (target <= %d)\n", fcc, designCapacity_mAh); - if (fcc > designCapacity_mAh * 3 / 2) { + if (fcc > designCapacity_mAh) { // RESET didn't fix FCC — the gauge IT algorithm is stubbornly // retaining its learned value. This typically resolves after one // full charge/discharge cycle. Software clamp in diff --git a/variants/lilygo_tdeck_pro/platformio.ini b/variants/lilygo_tdeck_pro/platformio.ini index 63a38525..2d52967f 100644 --- a/variants/lilygo_tdeck_pro/platformio.ini +++ b/variants/lilygo_tdeck_pro/platformio.ini @@ -105,13 +105,15 @@ lib_deps = ; --------------------------------------------------------------------------- ; Audio + BLE companion (audio-player hardware with BLE phone bridging) -; MAX_CONTACTS=500 is near BLE protocol ceiling (MAX_CONTACTS/2 sent as uint8_t, max 510) +; MAX_CONTACTS=2000 — protocol v11+ sends true capacity in extended DEVICE_INFO field. +; Older apps see 510 (sentinel 0xFF in legacy byte) and still work correctly. +; Contact + sort arrays allocated in PSRAM via BaseChatMesh::initContacts(). [env:meck_audio_ble] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=510 + -D MAX_CONTACTS=2000 -D MAX_GROUP_CHANNELS=20 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 @@ -155,7 +157,7 @@ build_flags = -D MECK_AUDIO_VARIANT -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.6.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.6.1.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -205,20 +207,22 @@ lib_ignore = ESP32 BLE Arduino ; 4G + BLE companion (4G modem hardware, no audio — GPIO conflict with PCM5102A) -; MAX_CONTACTS=500 is near BLE protocol ceiling (MAX_CONTACTS/2 sent as uint8_t, max 510) +; MAX_CONTACTS=2000 — protocol v11+ sends true capacity in extended DEVICE_INFO field. +; Older apps see 510 (sentinel 0xFF in legacy byte) and still work correctly. +; Contact + sort arrays allocated in PSRAM via BaseChatMesh::initContacts(). [env:meck_4g_ble] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=510 + -D MAX_CONTACTS=2000 -D MAX_GROUP_CHANNELS=20 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.6.4G"' + -D FIRMWARE_VERSION='"Meck v1.6.1.4G"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -254,7 +258,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.6.4G.WiFi"' + -D FIRMWARE_VERSION='"Meck v1.6.1.4G.WiFi"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -287,7 +291,7 @@ build_flags = -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1 -D MECK_OTA_UPDATE=1 - -D FIRMWARE_VERSION='"Meck v1.6.4G.SA"' + -D FIRMWARE_VERSION='"Meck v1.6.1.4G.SA"' build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} + + @@ -422,5 +426,4 @@ lib_ignore = ESPAsyncWebServer AsyncElegantOTA ESP32-audioI2S - esp32_codec2_arduino - \ No newline at end of file + esp32_codec2_arduino \ No newline at end of file