From 8aa0f0388e9fe1c0dc82497108fb9a67726d2e47 Mon Sep 17 00:00:00 2001 From: pelgraine Date: Sun, 5 Apr 2026 21:14:52 +1000 Subject: [PATCH] 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(); +