diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..8de9c1140 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,20 @@ +{ + "permissions": { + "allow": [ + "Bash(dir \"D:\\\\Git\\\\MeshCore\\\\variants\")", + "Bash(find /d/Git/MeshCore -name *.h -path */include/* -type f)", + "WebFetch(domain:github.com)", + "WebSearch", + "WebFetch(domain:wiki.seeedstudio.com)", + "WebFetch(domain:meshtastic.org)", + "WebFetch(domain:adrelien.com)", + "WebFetch(domain:manuals.plus)", + "WebFetch(domain:devices.esphome.io)", + "WebFetch(domain:files.seeedstudio.com)", + "WebFetch(domain:raw.githubusercontent.com)", + "WebFetch(domain:api.github.com)", + "Bash(grep -r \"IO_EXPANDER\\\\|i2c_addr\\\\|PCA9554\\\\|expander\" D:GitMeshCore.piolibdepsSenseCapIndicator-ESPNow_comp_radio_usbLovyanGFXsrc --include=*.hpp --include=*.cpp -l)", + "Bash(dir \"D:\\\\Git\\\\MeshCore\\\\.pio\\\\libdeps\\\\SenseCapIndicator-ESPNow_comp_radio_usb\\\\LovyanGFX\\\\src\\\\lgfx\\\\v1\\\\platforms\\\\esp32s3\" /b)" + ] + } +} diff --git a/examples/simple_secure_chat_ui/main.cpp b/examples/simple_secure_chat_ui/main.cpp index bf749a399..e74eea2a6 100644 --- a/examples/simple_secure_chat_ui/main.cpp +++ b/examples/simple_secure_chat_ui/main.cpp @@ -45,8 +45,10 @@ #include "esp_log.h" #include #include -#include +#ifndef SEEED_SENSECAP_INDICATOR +#include #include +#endif #include "../src/fonts/fonts.h" #include "../lvgl/lvgl.h" @@ -796,8 +798,8 @@ void initializeDisplay() { ESP_LOGI(TAG, "Initializing display..."); lcd.begin(); lcd.fillScreen(0x000000u); - lcd.setTextSize(2); - lcd.setRotation(1); + lcd.setTextSize(2); + lcd.setRotation(1); //lcd.setBrightness(127); } @@ -812,12 +814,18 @@ void initializeLVGL() { } void initializeMesh() { + Serial.println("[mesh] board.begin()"); board.begin(); - if (!radio_init()) { halt(); } + Serial.println("[mesh] radio_init()..."); + if (!radio_init()) { + Serial.println("[mesh] FATAL: radio_init() failed - halting"); + halt(); + } fast_rng.begin(radio_get_rng_seed()); + Serial.println("[mesh] SPIFFS.begin()"); #if defined(NRF52_PLATFORM) InternalFS.begin(); the_mesh.begin(InternalFS); @@ -830,20 +838,36 @@ void initializeMesh() { #else #error "need to define filesystem" #endif + Serial.println("[mesh] the_mesh.begin() done"); - radio_set_params(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR); - radio_set_tx_power(the_mesh.getTxPowerPref()); + float freq = the_mesh.getFreqPref(); + uint8_t txpwr = the_mesh.getTxPowerPref(); + Serial.printf("[mesh] Radio params: freq=%.3f MHz BW=%d SF=%d CR=%d TX=%d dBm\n", + freq, LORA_BW, LORA_SF, LORA_CR, txpwr); + radio_set_params(freq, LORA_BW, LORA_SF, LORA_CR); + radio_set_tx_power(txpwr); the_mesh.showWelcome(); - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvert(1200); // add slight delay + Serial.println("[mesh] Sending self-advert..."); + the_mesh.sendSelfAdvert(1200); + Serial.println("[mesh] Advert queued (1200ms delay)"); } void setup() { Serial.begin(115200); - initializeDisplay(); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + // Initialise I2C early so both the touch controller (FT5x06 @ 0x48) + // and the IO expander (TCA9535 @ 0x20) are ready before lcd.begin(). + Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + initializeDisplay(); delay(200); initializeLVGL(); @@ -878,11 +902,26 @@ void core_task(void *pvParameters) { vTaskSuspend(NULL); - ESP_LOGI(TAG, "MeshCore: Task running on core %d", xPortGetCoreID()); + Serial.printf("[core_task] Mesh loop started on core %d\n", xPortGetCoreID()); - while (1) { +#ifdef PIN_USER_BTN + bool btn_was_pressed = false; +#endif + + while (1) { the_mesh.loop(); rtc_clock.tick(); + +#ifdef PIN_USER_BTN + bool btn_pressed = (digitalRead(PIN_USER_BTN) == LOW); + if (btn_pressed && !btn_was_pressed) { + Serial.println("[btn] Press detected - restarting..."); + delay(50); + ESP.restart(); + } + btn_was_pressed = btn_pressed; +#endif + vTaskDelay(DELAY_CORE_TASK / portTICK_PERIOD_MS); } } diff --git a/examples/simple_secure_chat_ui/uiManager.cpp b/examples/simple_secure_chat_ui/uiManager.cpp index d6380e1fb..88d9e05c9 100644 --- a/examples/simple_secure_chat_ui/uiManager.cpp +++ b/examples/simple_secure_chat_ui/uiManager.cpp @@ -263,7 +263,7 @@ void UIManager::addPrivateChatBubble(const char *time_str, const char *msg, bool lv_obj_set_style_text_font(lbl_msg, &lv_font_arial_22, 0); // Fixed max width για wrap - lv_obj_set_width(lbl_msg, 400); // max πλάτος + lv_obj_set_width(lbl_msg, 270); // max πλάτος lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT); // αυτόματο ύψος // Bubble style @@ -358,7 +358,7 @@ void UIManager::handleContactClick(lv_event_t *e) void UIManager::addContactToUI(ContactInfo c) { - const int ROW_W = 200; + const int ROW_W = 130; const int ROW_H = 64; const int AVATAR = 44; const int PAD = 4; @@ -613,22 +613,10 @@ void UIManager::ui_Screen1_screen_init(void) ui_TabPageSettings = tabView.addTab("Ρυθμίσεις"); #endif - LvObj(ui_TabPageHome) - .scrollable(false) - .bgOpa(0) - .bgColor(0x000000); - LvObj(ui_TabPageContacts) - .scrollable(false) - .bgOpa(0) - .bgColor(0x000000); - LvObj(ui_TabPageChannels) - .scrollable(false) - .bgOpa(0) - .bgColor(0x000000); - LvObj(ui_TabPageSettings) - .scrollable(false) - .bgOpa(0) - .bgColor(0x000000); + lv_obj_clear_flag(ui_TabPageHome, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(ui_TabPageContacts, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(ui_TabPageChannels, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(ui_TabPageSettings, LV_OBJ_FLAG_SCROLLABLE); ui_ValueDate = LvLabel(ui_TabPageHome) .text("--- --/--/----") @@ -652,10 +640,10 @@ void UIManager::ui_Screen1_screen_init(void) .position(0, -100); ui_Contacts = LvList(ui_TabPageContacts) - .width(250) + .width(140) .height(400) .align(LV_ALIGN_CENTER) - .position(-274, 0) + .position(-155, 0) .transparent() .raw(); @@ -668,10 +656,10 @@ void UIManager::ui_Screen1_screen_init(void) lv_obj_set_style_border_width(ui_Contacts, 0, LV_PART_ITEMS); ui_ContactMessages = LvList(ui_TabPageContacts) - .width(500) + .width(310) .height(400) .align(LV_ALIGN_CENTER) - .position(124, 0) + .position(80, 0) .transparent() .raw(); @@ -694,19 +682,19 @@ void UIManager::ui_Screen1_screen_init(void) ui_Channels = LvDropdown(ui_TabPageChannels) .options("Public") - .width(291) + .width(200) .align(LV_ALIGN_CENTER) - .position(-243, -182) + .position(-135, -182) .clickable(true) .raw(); ui_ChannelMessages = LvList(ui_TabPageChannels) - .width(780) - .height(280) + .width(460) + .height(250) .align(LV_ALIGN_CENTER) .transparent() .padRow(10) - .position(0, 0) + .position(0, -10) .bgColor(0) .bgOpa(0) .border(0) @@ -718,7 +706,7 @@ void UIManager::ui_Screen1_screen_init(void) lv_obj_set_style_border_width(ui_ChannelMessages, 0, LV_PART_ITEMS); ui_ChannelDivider = LvObj(ui_TabPageChannels) - .size(780, 1) + .size(460, 1) .align(LV_ALIGN_CENTER) .position(0, 150) .bgColor(0x444444) @@ -741,9 +729,9 @@ void UIManager::ui_Screen1_screen_init(void) ui_ChannelInput = LvTextArea(ui_TabPageChannels) - .size(670, 40) + .size(360, 40) .align(LV_ALIGN_CENTER) - .position(-50, channelInputBaseY) + .position(-55, channelInputBaseY) .oneLine(true) #if defined(LANG_EN) .placeholder("Write message...") @@ -762,7 +750,7 @@ void UIManager::ui_Screen1_screen_init(void) ui_SendBtn = LvButton(ui_TabPageChannels) .size(90, 42) .align(LV_ALIGN_CENTER) - .position(350, channelInputBaseY) + .position(180, channelInputBaseY) .bgColor(0x3A7AFE) .onClick(s_onSendClick, this) .raw(); diff --git a/src/helpers/esp32/SenseCapHAL.h b/src/helpers/esp32/SenseCapHAL.h new file mode 100644 index 000000000..4a62121d0 --- /dev/null +++ b/src/helpers/esp32/SenseCapHAL.h @@ -0,0 +1,143 @@ +#pragma once + +// SenseCapHAL - Custom RadioLib 7.x HAL for the Seeed SenseCAP Indicator +// +// The SenseCAP Indicator routes SX1262 control pins through a TCA9535 +// 16-bit I2C IO expander (address 0x20) because GPIOs 0-15 are used by +// the 16-bit RGB display bus. +// +// Pin encoding matches LovyanGFX convention: (pin_index | IO_EXPANDER) +// IO_EXPANDER = 0x40 (defined as build flag in platformio.ini) +// +// SX1262 expander pin assignments (TCA9535 Port 0): +// pin 0 = NSS (EXPANDER_IO_RADIO_NSS) +// pin 1 = RESET (EXPANDER_IO_RADIO_RST) +// pin 2 = BUSY (EXPANDER_IO_RADIO_BUSY) +// pin 3 = DIO1 (EXPANDER_IO_RADIO_DIO_1) +// +// DIO1 interrupt: TCA9535 /INT output → GPIO 42 (IO_EXPANDER_IRQ) +// The /INT pin fires FALLING when any input changes. +// +// TCA9535 register map (Port 0 only): +// 0x00 Input Port 0 (read) +// 0x02 Output Port 0 (write; 1=HIGH, 0=LOW) +// 0x06 Config Port 0 (0=output, 1=input) + +#include +#include +#include + +class SenseCapHAL : public ArduinoHal { + TwoWire* _wire; + uint8_t _addr; // 7-bit I2C address of TCA9535 (0x20) + uint8_t _out0; // cached Output Port 0 latch (all HIGH = de-asserted) + uint8_t _cfg0; // cached Config Port 0 (all 1 = input initially) + int _sclk, _miso, _mosi; // SPI data pins + + // A pin is on the expander when its upper nibble equals IO_EXPANDER + static bool isExp(uint32_t pin) { + return (pin & ~0x0Fu) == (uint32_t)IO_EXPANDER; + } + // Bit mask within TCA9535 port register + static uint8_t expBit(uint32_t pin) { + return (uint8_t)(1u << (pin & 0x07u)); + } + // Pins 0-7 are Port 0; pins 8-15 are Port 1 + static bool isPort0(uint32_t pin) { + return (pin & 0x08u) == 0; + } + + void writeReg(uint8_t reg, uint8_t val) { + _wire->beginTransmission(_addr); + _wire->write(reg); + _wire->write(val); + _wire->endTransmission(); + } + uint8_t readReg(uint8_t reg) { + _wire->beginTransmission(_addr); + _wire->write(reg); + _wire->endTransmission(false); + _wire->requestFrom(_addr, (uint8_t)1); + return _wire->available() ? _wire->read() : 0xFF; + } + +public: + SenseCapHAL(SPIClass& spi, int sclk, int miso, int mosi, + uint8_t i2c_addr = 0x20, TwoWire* wire = &Wire) + : ArduinoHal(spi, SPISettings(2000000, MSBFIRST, SPI_MODE0)) + , _wire(wire), _addr(i2c_addr) + , _out0(0xFF) // all HIGH (NSS, RESET de-asserted) + , _cfg0(0xFF) // all inputs initially + , _sclk(sclk), _miso(miso), _mosi(mosi) + {} + + // Call once Wire is running — sets TCA9535 Port 0: all outputs HIGH + void initExpander() { + writeReg(0x02, _out0); // Output Port 0: all HIGH + writeReg(0x06, _cfg0); // Config Port 0: all inputs (RadioLib will reconfigure) + Serial.printf("[SenseCapHAL] TCA9535@0x%02X init: out=0x%02X cfg=0x%02X\n", + _addr, _out0, _cfg0); + } + + // ── Overrides ────────────────────────────────────────────────────────── + + // Initialize SPI with the correct pin numbers for SenseCAP Indicator + void spiBegin() override { + this->spi->begin(_sclk, _miso, _mosi); + Serial.printf("[SenseCapHAL] SPI begin: SCLK=%d MISO=%d MOSI=%d\n", + _sclk, _miso, _mosi); + } + + void pinMode(uint32_t pin, uint32_t mode) override { + if (isExp(pin) && isPort0(pin)) { + if (mode == OUTPUT) _cfg0 &= ~expBit(pin); + else _cfg0 |= expBit(pin); + writeReg(0x06, _cfg0); + } else { + ArduinoHal::pinMode(pin, mode); + } + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if (isExp(pin) && isPort0(pin)) { + if (value) _out0 |= expBit(pin); + else _out0 &= ~expBit(pin); + writeReg(0x02, _out0); + } else { + ArduinoHal::digitalWrite(pin, value); + } + } + + uint32_t digitalRead(uint32_t pin) override { + if (isExp(pin) && isPort0(pin)) { + return (readReg(0x00) & expBit(pin)) ? 1 : 0; + } + return ArduinoHal::digitalRead(pin); + } + + // DIO1 is expander pin 3; redirect the interrupt to IO_EXPANDER_IRQ (GPIO 42). + // TCA9535 /INT fires FALLING when any input changes (DIO1 going HIGH = packet ready). + void attachInterrupt(uint32_t pin, void (*cb)(void), uint32_t mode) override { + if (isExp(pin)) { + ::pinMode(IO_EXPANDER_IRQ, INPUT_PULLUP); + ::attachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ), cb, FALLING); + Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d (FALLING)\n", IO_EXPANDER_IRQ); + } else { + ArduinoHal::attachInterrupt(pin, cb, mode); + } + } + + void detachInterrupt(uint32_t pin) override { + if (isExp(pin)) { + ::detachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ)); + } else { + ArduinoHal::detachInterrupt(pin); + } + } + + // Scan for the TCA9535 on I2C and log the result + bool scanExpander() { + _wire->beginTransmission(_addr); + return (_wire->endTransmission() == 0); + } +}; diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index 0e530f127..736d57bbc 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -42,11 +42,17 @@ build_flags = -D CORE_DEBUG_LEVEL=4 ; 0: None, 1: Error, 2: Warn, 3: Info, 4: Debug, 5: Verbose -D LV_CONF_PATH=lv_conf.h -D SEEED_SENSECAP_INDICATOR - -D RADIO_CLASS=SX1262 + -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D PIN_USER_BTN=38 -D LANG_GR + -D LORA_FREQ=869.618 + -D LORA_BW=62.5 + -D LORA_SF=8 + -D LORA_CR=8 -D LORA_TX_POWER=20 + -D SX126X_DIO3_TCXO_VOLTAGE=2.4 + -D SX126X_DIO2_AS_RF_SWITCH=true -D ADVERT_NAME='"SenseCap Client"' -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=8 @@ -55,16 +61,13 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} - +<../examples/companion_radio/*.cpp> - ;+<../examples/simple_secure_chat_ui/*.cpp> + +<../examples/simple_secure_chat_ui/*.cpp> + + lib_deps = ${SenseCapIndicator-ESPNow.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.15 fbiego/ESP32Time@^2.0.6 lvgl/lvgl@8.3.11 lovyan03/LovyanGFX@^1.1.16 bitbank2/PNGdec@^1.1.6 - ;tamctec/TAMC_GT911@^1.0.2 densaugeo/base64 @ ~1.4.0 diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index b6608bbbe..fa248e484 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -2,55 +2,72 @@ #include "target.h" ESP32Board board; -//SenseCapIndicatorBoard board; -static SPIClass spi(FSPI); +// ── SX1262 SPI data pins (hardware FSPI / SPI2) ──────────────────────── +// Shared with the ST7701 display config SPI (bit-banged by LovyanGFX only +// during lcd.begin(), so no runtime conflict). +#define LORA_SCLK 41 +#define LORA_MISO 47 +#define LORA_MOSI 48 + +// ── SX1262 control pins via TCA9535 I2C IO Expander (7-bit addr 0x20) ── +// Encoded as (pin_index | IO_EXPANDER) per LovyanGFX / Seeed convention. +// IO_EXPANDER = 0x40 and IO_EXPANDER_IRQ = 42 defined in platformio.ini +#define LORA_NSS (0 | IO_EXPANDER) // TCA9535 port-0 pin 0 (0x40) +#define LORA_RESET (1 | IO_EXPANDER) // TCA9535 port-0 pin 1 (0x41) +#define LORA_BUSY (2 | IO_EXPANDER) // TCA9535 port-0 pin 2 (0x42) +#define LORA_DIO1 (3 | IO_EXPANDER) // TCA9535 port-0 pin 3 (0x43) + // → interrupt fires on GPIO 42 -// SX1262 pins για SenseCAP Indicator Meshtastic edition -#define LORA_SCLK 5 -#define LORA_MISO 4 -#define LORA_MOSI 6 -#define LORA_NSS 7 -#define LORA_DIO1 2 -#define LORA_RESET 8 -#define LORA_BUSY 3 +static SPIClass spi(FSPI); -Module* module = new Module( - LORA_NSS, - LORA_DIO1, - LORA_RESET, - LORA_BUSY, - spi -); +// Custom RadioLib HAL: routes expander pins through TCA9535 (I2C 0x20) +// and inits SPI with the correct pins on first use. +static SenseCapHAL radio_hal(spi, LORA_SCLK, LORA_MISO, LORA_MOSI, 0x20, &Wire); -SX1262 radio(module); +// SX1262 module using the custom HAL +RADIO_CLASS radio = new Module(&radio_hal, LORA_NSS, LORA_DIO1, LORA_RESET, LORA_BUSY); -WRAPPER_CLASS radio_driver(&radio, board); +WRAPPER_CLASS radio_driver(radio, board); -ESP32RTCClock fallback_clock; -// AutoDiscoverRTCClock rtc_clock(fallback_clock); +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors; -// #ifdef DISPLAY_CLASS -// DISPLAY_CLASS display(&(board.periph_power)); -// MomentaryButton user_btn(PIN_USER_BTN, 1000, true); -// #endif - +// ── radio_init ───────────────────────────────────────────────────────── bool radio_init() { - fallback_clock.begin(); - rtc_clock.begin(Wire); + Serial.println("[radio_init] Starting..."); + Serial.printf("[radio_init] SPI : SCLK=%d MISO=%d MOSI=%d\n", + LORA_SCLK, LORA_MISO, LORA_MOSI); + Serial.printf("[radio_init] Expander (0x20): NSS=pin%d RST=pin%d BUSY=pin%d DIO1=pin%d\n", + LORA_NSS & 0x0F, LORA_RESET & 0x0F, LORA_BUSY & 0x0F, LORA_DIO1 & 0x0F); + Serial.printf("[radio_init] DIO1 interrupt → GPIO %d\n", IO_EXPANDER_IRQ); + + // Wire must be up before TCA9535 access. + // (lcd.begin() may have already called Wire.begin, but re-init is safe.) + Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); + + // Verify TCA9535 is present on the bus + if (radio_hal.scanExpander()) { + Serial.println("[radio_init] TCA9535 FOUND at 0x20"); + } else { + Serial.println("[radio_init] WARNING: TCA9535 NOT FOUND at 0x20 – check I2C wiring"); + } + + // Set TCA9535 Port 0 defaults: all outputs HIGH (NSS/RESET de-asserted) + radio_hal.initExpander(); - spi.begin(LORA_SCLK, LORA_MISO, LORA_MOSI); - - return radio_driver.std_init(&spi); - - #if defined(P_LORA_SCLK) - return radio.std_init(&spi); - #else - return radio.std_init(); - #endif + fallback_clock.begin(); + Serial.println("[radio_init] RTC initialized"); + + // std_init() calls radio.begin() internally, which calls our spiBegin() + // to initialise the FSPI bus with pins 41/47/48, then communicates with + // the SX1262 using our expander-aware digitalWrite/digitalRead. + bool ok = radio.std_init(); + Serial.printf("[radio_init] std_init result: %s\n", ok ? "OK" : "FAILED"); + return ok; } uint32_t radio_get_rng_seed() { @@ -71,4 +88,4 @@ void radio_set_tx_power(uint8_t dbm) { mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); -} \ No newline at end of file +} diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h index f15f44212..805c63456 100644 --- a/variants/sensecap_indicator-espnow/target.h +++ b/variants/sensecap_indicator-espnow/target.h @@ -2,11 +2,13 @@ #include #include +#include #include #include #include #include +#include // TCA9535 IO expander HAL for RadioLib #include #include @@ -36,7 +38,7 @@ extern CustomSX1262Wrapper radio_driver; // ------------------------------------------------- // RTC // ------------------------------------------------- -extern ESP32RTCClock rtc_clock; +extern AutoDiscoverRTCClock rtc_clock; // -------------------------------------------------