ThinkNode M7 (#8077)

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* rename variant and add guard macros

* older G3 operational. M7 next.

* Split out G3 and M7 to different variants. Completely new PCB design. The G3 stays on 'PRIVATE_HW'

* Define button behaviour and use all of the device flash

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
This commit is contained in:
Thomas Göttgens
2026-05-11 18:33:13 +02:00
committed by GitHub
parent a23f923e64
commit 8e99ffbe7e
25 changed files with 340 additions and 22 deletions

42
boards/ThinkNode-M7.json Normal file
View File

@@ -0,0 +1,42 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-D BOARD_HAS_PSRAM",
"-D ARDUINO_USB_CDC_ON_BOOT=0",
"-D ARDUINO_USB_MODE=0",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=0"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qio_opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "ELECROW-ThinkNode-M7"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "ELECROW ThinkNode M7",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 524288,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.elecrow.com",
"vendor": "ELECROW"
}

View File

@@ -151,7 +151,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
if (!this->_enabled)
return false;
if ((this->_server == NULL && this->_ip == INADDR_NONE) || this->_port == 0)
if ((this->_server == NULL && this->_ip == IPAddress(0, 0, 0, 0)) || this->_port == 0)
return false;
// Check priority against priMask values.

View File

@@ -13,6 +13,11 @@ extern MemGet memGet;
#define LED_STATE_ON 1
#endif
// WIFI LED
#ifndef WIFI_STATE_ON
#define WIFI_STATE_ON 1
#endif
// -----------------------------------------------------------------------------
// DEBUG
// -----------------------------------------------------------------------------
@@ -147,7 +152,9 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN
#define defaultBLEPin 123456
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && defined(USE_CH390D)
#include <ESP32_CH390.h>
#elif HAS_ETHERNET && !defined(USE_WS5500)
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET

View File

@@ -333,6 +333,12 @@ void InputBroker::Init()
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
#if defined(ELECROW_ThinkNode_M7)
userConfigNoScreen.longLongPressTime = 15 * 1000;
userConfigNoScreen.longLongPress = INPUT_BROKER_FACTORY_RST;
#else
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
#endif
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;

View File

@@ -25,6 +25,7 @@ enum input_broker_event {
INPUT_BROKER_USER_PRESS,
INPUT_BROKER_ALT_PRESS,
INPUT_BROKER_ALT_LONG,
INPUT_BROKER_FACTORY_RST = 0x9a,
INPUT_BROKER_SHUTDOWN = 0x9b,
INPUT_BROKER_GPS_TOGGLE = 0x9e,
INPUT_BROKER_SEND_PING = 0xaf,

View File

@@ -59,12 +59,12 @@ NimbleBluetooth *nimbleBluetooth = nullptr;
NRF52Bluetooth *nrf52Bluetooth = nullptr;
#endif
#if HAS_WIFI || defined(USE_WS5500)
#if HAS_WIFI || defined(USE_WS5500) || defined(USE_CH390D)
#include "mesh/api/WiFiServerAPI.h"
#include "mesh/wifi/WiFiAPClient.h"
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include "mesh/api/ethServerAPI.h"
#include "mesh/eth/ethClient.h"
#endif
@@ -335,7 +335,7 @@ void setup()
#ifdef WIFI_LED
pinMode(WIFI_LED, OUTPUT);
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
#ifdef BLE_LED

View File

@@ -32,7 +32,7 @@ template class LR20x0Interface<LR2021>;
template class SX126xInterface<STM32WLx>;
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include "api/ethServerAPI.h"
template class ServerAPI<EthernetClient>;
template class APIServerPort<ethServerAPI, EthernetServer>;

View File

@@ -1,7 +1,7 @@
#include "configuration.h"
#include <Arduino.h>
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include "ethServerAPI.h"

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ServerAPI.h"
#ifndef USE_WS5500
#if !defined(USE_WS5500) && !defined(USE_CH390D)
#include <RAK13800_W5100S.h>
/**

View File

@@ -9,7 +9,7 @@
#include <RAK13800_W5100S.h>
#include <SPI.h>
#if HAS_NETWORKING
#if HAS_NETWORKING && !defined(USE_WS5500) && !defined(USE_CH390D)
#ifndef DISABLE_NTP
#include <NTPClient.h>

View File

@@ -15,6 +15,12 @@
#define ETH ETH2
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_CH390D)
#include "ESP32_CH390.h"
#include "hal/spi_types.h"
#define ETH CH390
#endif // HAS_ETHERNET
#include <WiFiUdp.h>
#ifdef ARCH_ESP32
#if !MESHTASTIC_EXCLUDE_WEBSERVER
@@ -56,12 +62,43 @@ unsigned long lastrun_ntp = 0;
bool needReconnect = true; // If we create our reconnector, run it once at the beginning
bool isReconnecting = false; // If we are currently reconnecting
#if defined(USE_WS5500) || defined(USE_CH390D)
static volatile bool ethNetworkConnectedPending = false;
#endif
WiFiUDP syslogClient;
meshtastic::Syslog syslog(syslogClient);
Periodic *wifiReconnect;
#if defined(USE_WS5500) || defined(USE_CH390D)
static void onNetworkConnected();
static uint32_t lastEthIP = 0;
static int32_t ethNetworkConnectedPoll()
{
if (ethNetworkConnectedPending) {
ethNetworkConnectedPending = false;
uint32_t ip = (uint32_t)ETH.localIP();
bool ipChanged = APStartupComplete && ip != 0 && ip != lastEthIP;
onNetworkConnected();
if (ipChanged) {
LOG_INFO("Ethernet IP changed (%u.%u.%u.%u), restarting mDNS", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff,
(ip >> 24) & 0xff);
MDNS.end();
if (MDNS.begin("Meshtastic")) {
MDNS.addService("meshtastic", "tcp", SERVER_API_DEFAULT_PORT);
MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV));
}
}
if (ip != 0)
lastEthIP = ip;
}
return 500;
}
#endif
#ifdef USE_WS5500
// Startup Ethernet
bool initEthernet()
@@ -72,6 +109,38 @@ bool initEthernet()
#if !MESHTASTIC_EXCLUDE_WEBSERVER
createSSLCert(); // For WebServer
#endif
new concurrency::Periodic("EthConnect", ethNetworkConnectedPoll);
return true;
}
return false;
}
#endif
#ifdef USE_CH390D
// Startup Ethernet
bool initEthernet()
{
// Configure CH390
ch390_config_t ch390_conf = CH390_DEFAULT_CONFIG();
ch390_conf.spi_host = SPI3_HOST;
ch390_conf.spi_cs_gpio = ETH_CS_PIN;
ch390_conf.spi_sck_gpio = ETH_SCLK_PIN;
ch390_conf.spi_mosi_gpio = ETH_MOSI_PIN;
ch390_conf.spi_miso_gpio = ETH_MISO_PIN;
ch390_conf.int_gpio = ETH_INT_PIN;
#ifdef ETH_RST_PIN
ch390_conf.reset_gpio = ETH_RST_PIN;
#else
ch390_conf.reset_gpio = -1;
#endif
ch390_conf.spi_clock_mhz = 20;
if ((config.network.eth_enabled) && (ETH.begin(ch390_conf))) {
WiFi.onEvent(WiFiEvent);
#if !MESHTASTIC_EXCLUDE_WEBSERVER
createSSLCert(); // For WebServer
#endif
new concurrency::Periodic("EthConnect", ethNetworkConnectedPoll);
return true;
}
@@ -234,7 +303,7 @@ bool isWifiAvailable()
if (config.network.wifi_enabled && (config.network.wifi_ssid[0])) {
return true;
#ifdef USE_WS5500
#if defined(USE_WS5500) || defined(USE_CH390D)
} else if (config.network.eth_enabled) {
return true;
#endif
@@ -384,13 +453,13 @@ static void WiFiEvent(WiFiEvent_t event)
#endif
}
#ifdef WIFI_LED
digitalWrite(WIFI_LED, HIGH);
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
LOG_INFO("Disconnected from WiFi access point");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
#if HAS_UDP_MULTICAST
if (udpHandler) {
@@ -452,13 +521,13 @@ static void WiFiEvent(WiFiEvent_t event)
case ARDUINO_EVENT_WIFI_AP_START:
LOG_INFO("WiFi access point started");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, HIGH);
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
LOG_INFO("WiFi access point stopped");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
@@ -494,18 +563,18 @@ static void WiFiEvent(WiFiEvent_t event)
LOG_INFO("Ethernet disconnected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
#ifdef USE_WS5500
#if defined(USE_WS5500) || defined(USE_CH390D)
LOG_INFO("Obtained IP address: %s, %u Mbps, %s", ETH.localIP().toString().c_str(), ETH.linkSpeed(),
ETH.fullDuplex() ? "FULL_DUPLEX" : "HALF_DUPLEX");
onNetworkConnected();
ethNetworkConnectedPending = true;
#endif
break;
case ARDUINO_EVENT_ETH_GOT_IP6:
#ifdef USE_WS5500
#if defined(USE_WS5500) || defined(USE_CH390D)
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
LOG_INFO("Obtained Local IP6 address: %s", ETH.linkLocalIPv6().toString().c_str());
LOG_INFO("Obtained GlobalIP6 address: %s", ETH.globalIPv6().toString().c_str());
#else
#elif defined(USE_WS5500)
LOG_INFO("Obtained IP6 address: %s", ETH.localIPv6().toString().c_str());
#endif
#endif

View File

@@ -26,7 +26,7 @@ bool isWifiAvailable();
uint8_t getWifiDisconnectReason();
#ifdef USE_WS5500
#if defined(USE_WS5500) || defined(USE_CH390D)
// Startup Ethernet
bool initEthernet();
#endif

View File

@@ -1318,7 +1318,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
}
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
conn.has_ethernet = true;
conn.ethernet.has_status = true;
if (Ethernet.linkStatus() == LinkON) {

View File

@@ -115,6 +115,17 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
case INPUT_BROKER_SHUTDOWN:
shutdownAtMsec = millis();
return true;
// factory reset
case INPUT_BROKER_FACTORY_RST:
disableBluetooth();
LOG_INFO("Initiate full factory reset");
nodeDB->factoryReset(true);
// reboot(DEFAULT_REBOOT_SECONDS);
LOG_INFO("Reboot in %d seconds", DEFAULT_REBOOT_SECONDS);
if (screen)
screen->showSimpleBanner("Rebooting...", 0); // stays on screen
rebootAtMsec = (DEFAULT_REBOOT_SECONDS < 0) ? 0 : (millis() + DEFAULT_REBOOT_SECONDS * 1000);
return true;
default:
// No other input events handled here

View File

@@ -22,6 +22,9 @@
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#elif HAS_ETHERNET && defined(USE_CH390D)
#include "ESP32_CH390.h"
#define ETH CH390
#endif // HAS_ETHERNET
#include "Default.h"
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
@@ -344,6 +347,9 @@ inline bool isConnectedToNetwork()
#ifdef USE_WS5500
if (ETH.connected())
return true;
#elif defined(USE_CH390D)
if (ETH.isConnected())
return true;
#endif
#if HAS_WIFI

View File

@@ -15,7 +15,7 @@
#include <WiFiClientSecure.h>
#endif
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include <EthernetClient.h>
#endif

View File

@@ -146,6 +146,8 @@
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M2
#elif defined(ELECROW_ThinkNode_M5)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M5
#elif defined(ELECROW_ThinkNode_M7)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M7
#elif defined(ESP32_S3_PICO)
#define HW_VENDOR meshtastic_HardwareModel_ESP32_S3_PICO
#elif defined(SENSELORA_S3)

View File

@@ -32,7 +32,7 @@ void variant_shutdown() {}
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
#ifdef USE_WS5500
#if defined(USE_WS5500) || defined(USE_CH390D)
if ((config.bluetooth.enabled == true) && (config.network.wifi_enabled == false))
#elif HAS_WIFI
if (!isWifiAvailable() && config.bluetooth.enabled == true)

View File

@@ -0,0 +1,26 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// The default Wire will be mapped to PMU and RTC
static const uint8_t SDA = 17;
static const uint8_t SCL = 18;
// Default SPI will be mapped to Radio
static const uint8_t SS = 39;
static const uint8_t MOSI = 40;
static const uint8_t MISO = 41;
static const uint8_t SCK = 42;
// #define SPI_MOSI (11)
// #define SPI_SCK (10)
// #define SPI_MISO (9)
// #define SPI_CS (12)
// #define SDCARD_CS SPI_CS
#endif /* Pins_Arduino_h */

View File

@@ -0,0 +1,20 @@
[env:thinknode_g3]
extends = esp32s3_base
board = ESP32-S3-WROOM-1-N4
board_build.psram_type = opi
build_flags =
${esp32s3_base.build_flags}
-D HAS_UDP_MULTICAST=1
-D BOARD_HAS_PSRAM
-D PRIVATE_HW
-I variants/esp32s3/ELECROW-ThinkNode-G3
-mfix-esp32-psram-cache-issue
lib_ignore =
Ethernet
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=github-tags depName=ESP32-CH390 packageName=meshtastic/ESP32-CH390
https://github.com/meshtastic/ESP32-CH390/archive/refs/tags/v1.0.1.zip

View File

@@ -0,0 +1,36 @@
#define HAS_GPS 0
#define HAS_WIRE 0
#define I2C_NO_RESCAN
#define WIFI_LED 5
#define WIFI_STATE_ON 0
#define LED_PIN 6
#define LED_STATE_ON 0
#define BUTTON_PIN 4
#define LORA_SCK 42
#define LORA_MISO 41
#define LORA_MOSI 40
#define LORA_CS 39
#define LORA_RESET 21
#define USE_SX1262
#define SX126X_CS LORA_CS
#define SX126X_DIO1 15
#define SX126X_BUSY 47
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define PIN_POWER_EN 45
#define HAS_ETHERNET 1
#define USE_CH390D 1
#define ETH_MISO_PIN 12
#define ETH_MOSI_PIN 11
#define ETH_SCLK_PIN 13
#define ETH_CS_PIN 14
#define ETH_INT_PIN 10
#define ETH_RST_PIN 9
// #define ETH_ADDR 1

View File

@@ -0,0 +1,19 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// The default Wire will be mapped to PMU and RTC
static const uint8_t SDA = 17;
static const uint8_t SCL = 18;
// Default SPI is the LR1110 radio bus
static const uint8_t SS = 12;
static const uint8_t MOSI = 10;
static const uint8_t MISO = 9;
static const uint8_t SCK = 11;
#endif /* Pins_Arduino_h */

View File

@@ -0,0 +1,19 @@
[env:thinknode_m7]
extends = esp32s3_base
board = ThinkNode-M7
build_flags =
${esp32s3_base.build_flags}
-D ELECROW_ThinkNode_M7
-D HAS_UDP_MULTICAST=1
-D BOARD_HAS_PSRAM
-I variants/esp32s3/ELECROW-ThinkNode-M7
-mfix-esp32-psram-cache-issue
lib_ignore =
Ethernet
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=github-tags depName=ESP32-CH390 packageName=meshtastic/ESP32-CH390
https://github.com/meshtastic/ESP32-CH390/archive/refs/tags/v1.0.1.zip

View File

@@ -0,0 +1,11 @@
#include "RadioLib.h"
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
};

View File

@@ -0,0 +1,43 @@
#define HAS_GPS 0
#define HAS_WIRE 0
#define HAS_SCREEN 0
#define I2C_NO_RESCAN
#define UART_TX 43
#define UART_RX 44
#define WIFI_LED 3
#define WIFI_STATE_ON 0
#define LED_PIN 46
#define LED_STATE_ON 0
#define BUTTON_PIN 4
#define BUTTON_ACTIVE_LOW true
#define BUTTON_ACTIVE_PULLUP true
#define LORA_SCK 11
#define LORA_MISO 9
#define LORA_MOSI 10
#define LORA_CS 12
#define LORA_RESET 39
#define USE_LR1110
#define LR1110_SPI_SCK_PIN LORA_SCK
#define LR1110_SPI_MISO_PIN LORA_MISO
#define LR1110_SPI_MOSI_PIN LORA_MOSI
#define LR1110_SPI_NSS_PIN LORA_CS
#define LR1110_IRQ_PIN 38
#define LR1110_BUSY_PIN 13
#define LR1110_NRESET_PIN LORA_RESET
#define LR11X0_DIO3_TCXO_VOLTAGE 1.8
#define LR11X0_DIO_AS_RF_SWITCH
#define HAS_ETHERNET 1
#define USE_CH390D 1
#define ETH_MISO_PIN 14
#define ETH_MOSI_PIN 48
#define ETH_SCLK_PIN 47
#define ETH_CS_PIN 21
#define ETH_INT_PIN 45
// #define ETH_ADDR 1