Browse Source

wip

pull/2568/head
Christos Themelis 3 months ago
parent
commit
4636a88078
  1. 20
      .claude/settings.local.json
  2. 61
      examples/simple_secure_chat_ui/main.cpp
  3. 50
      examples/simple_secure_chat_ui/uiManager.cpp
  4. 143
      src/helpers/esp32/SenseCapHAL.h
  5. 13
      variants/sensecap_indicator-espnow/platformio.ini
  6. 93
      variants/sensecap_indicator-espnow/target.cpp
  7. 4
      variants/sensecap_indicator-espnow/target.h

20
.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)"
]
}
}

61
examples/simple_secure_chat_ui/main.cpp

@ -45,8 +45,10 @@
#include "esp_log.h"
#include <lvgl.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#ifndef SEEED_SENSECAP_INDICATOR
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#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);
}
}

50
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();

143
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 <RadioLib.h>
#include <Wire.h>
#include <SPI.h>
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);
}
};

13
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>
+<fonts/*.c>
+<UI/*.c>
lib_deps =
${SenseCapIndicator-ESPNow.lib_deps}
adafruit/Adafruit SSD1306 @ ^2.5.15
fbiego/ESP32Time@^2.0.6
lvgl/[email protected]
lovyan03/LovyanGFX@^1.1.16
bitbank2/PNGdec@^1.1.6
;tamctec/TAMC_GT911@^1.0.2
densaugeo/base64 @ ~1.4.0

93
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);
}
}

4
variants/sensecap_indicator-espnow/target.h

@ -2,11 +2,13 @@
#include <Arduino.h>
#include <RadioLib.h>
#include <Wire.h>
#include <helpers/ESP32Board.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/esp32/SenseCapHAL.h> // TCA9535 IO expander HAL for RadioLib
#include <helpers/SensorManager.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
@ -36,7 +38,7 @@ extern CustomSX1262Wrapper radio_driver;
// -------------------------------------------------
// RTC
// -------------------------------------------------
extern ESP32RTCClock rtc_clock;
extern AutoDiscoverRTCClock rtc_clock;
// -------------------------------------------------

Loading…
Cancel
Save