diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 080e70d08b..8057bc70a7 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,7 +1,6 @@
{
- // See http://go.microsoft.com/fwlink/?LinkId=827846
- // for the documentation about the extensions.json format
"recommendations": [
+ "pioarduino.pioarduino-ide",
"platformio.platformio-ide"
],
"unwantedRecommendations": [
diff --git a/boards/t_eth_elite.json b/boards/t_eth_elite.json
new file mode 100644
index 0000000000..e2b039d1da
--- /dev/null
+++ b/boards/t_eth_elite.json
@@ -0,0 +1,51 @@
+{
+ "build": {
+ "arduino":{
+ "ldscript": "esp32s3_out.ld",
+ "partitions": "default.csv",
+ "memory_type": "qio_qspi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_USB_MODE=1",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [
+ [
+ "0x303A",
+ "0x1001"
+ ]
+ ],
+ "mcu": "esp32s3",
+ "variant": "esp32s3"
+ },
+ "connectivity": [
+ "wifi"
+ ],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": [
+ "esp-builtin"
+ ],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": [
+ "arduino",
+ "espidf"
+ ],
+ "name": "LilyGo T-ETH-ELite (16MB Flash 8MB PSRAM)",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 8388608,
+ "maximum_size": 16777216,
+ "require_upload_port": true,
+ "speed": 460800
+ },
+ "url": "https://lilygo.cc/products/t-eth-elite-1",
+ "vendor": "LilyGo"
+ }
\ No newline at end of file
diff --git a/docs/cli_commands.md b/docs/cli_commands.md
index 1d3430db21..7a977a1b3f 100644
--- a/docs/cli_commands.md
+++ b/docs/cli_commands.md
@@ -19,6 +19,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- [GPS](#gps-when-gps-support-is-compiled-in)
- [Sensors](#sensors-when-sensor-support-is-compiled-in)
- [Bridge](#bridge-when-bridge-support-is-compiled-in)
+ - [Ethernet](#ethernet-when-use_ethernet-is-compiled-in)
---
@@ -816,6 +817,19 @@ region save
---
+#### View the bridge type
+**Usage:**
+- `get bridge.type`
+
+**Returns:**
+- `none`: No bridge available/compiled
+- `rs232`: RS-232 serial bridge
+- `espnow`: ESP-NOW wireless bridge
+
+**Note:** This is read-only; bridge type is determined at compile time
+
+---
+
#### View the bridge source
**Usage:**
- `get bridge.source`
@@ -881,3 +895,69 @@ region save
**Default:** Varies by board
---
+
+### Ethernet (When USE_ETHERNET is compiled in)
+
+#### View or change this node's Ethernet IP address
+**Usage:**
+- `get ip`
+- `set ip
`
+
+**Parameters:**
+- `address`: IPv4 address (e.g., `192.168.254.21`)
+
+**Set by build flag:** `ETH_STATIC_IP`
+
+**Default:** `0.0.0.0` (uses DHCP if not configured)
+
+**Note:** Requires reboot to apply. If no valid IP is set, device will use DHCP at boot.
+
+---
+
+#### View or change this node's Ethernet gateway
+**Usage:**
+- `get gw`
+- `set gw `
+
+**Parameters:**
+- `address`: IPv4 gateway address (e.g., `192.168.254.254`)
+
+**Set by build flag:** `ETH_GATEWAY`
+
+**Default:** `0.0.0.0`
+
+**Note:** Requires reboot to apply
+
+---
+
+#### View or change this node's Ethernet subnet mask
+**Usage:**
+- `get subnet`
+- `set subnet `
+
+**Parameters:**
+- `address`: Subnet mask in dotted decimal notation (e.g., `255.255.255.0`)
+
+**Set by build flag:** `ETH_SUBNET`
+
+**Default:** `0.0.0.0`
+
+**Note:** Requires reboot to apply
+
+---
+
+#### View or change this node's Ethernet DNS server
+**Usage:**
+- `get dns`
+- `set dns `
+
+**Parameters:**
+- `address`: IPv4 DNS address (e.g., `8.8.8.8`)
+
+**Set by build flag:** `ETH_DNS`
+
+**Default:** `0.0.0.0`
+
+**Note:** Requires reboot to apply. Configures DNS1 (primary DNS server).
+
+---
diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp
index eff9efca47..c0cdf3d1bc 100644
--- a/examples/companion_radio/main.cpp
+++ b/examples/companion_radio/main.cpp
@@ -2,6 +2,11 @@
#include
#include "MyMesh.h"
+#if defined(ESP32) && defined(TCP_CONSOLE_PORT) && defined(ADMIN_PASSWORD)
+ #include
+ TCPConsole tcp_console(nullptr); // prefs set in setup()
+#endif
+
// Believe it or not, this std C function is busted on some platforms!
static uint32_t _atoi(const char* sp) {
uint32_t n = 0;
@@ -35,7 +40,7 @@ static uint32_t _atoi(const char* sp) {
#endif
#ifdef ESP32
- #ifdef WIFI_SSID
+ #if defined(WIFI_SSID) || defined(USE_ETHERNET)
#include
SerialWifiInterface serial_interface;
#ifndef TCP_PORT
@@ -109,6 +114,10 @@ void setup() {
Serial.begin(115200);
board.begin();
+
+ #if defined(ESP32) && defined(TCP_CONSOLE_PORT) && defined(ADMIN_PASSWORD)
+ tcp_console.begin();
+ #endif
#ifdef DISPLAY_CLASS
DisplayDriver* disp = NULL;
@@ -122,7 +131,7 @@ void setup() {
disp->endFrame();
}
#endif
-
+
if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed());
@@ -143,6 +152,9 @@ void setup() {
#endif
store.begin();
the_mesh.begin(
+
+ tcp_console.setPrefs(the_mesh.getNodePrefs());
+
#ifdef DISPLAY_CLASS
disp != NULL
#else
@@ -194,9 +206,12 @@ void setup() {
);
#ifdef WIFI_SSID
- board.setInhibitSleep(true); // prevent sleep when WiFi is active
+ board.setInhibitSleep(true);
WiFi.begin(WIFI_SSID, WIFI_PWD);
serial_interface.begin(TCP_PORT);
+#elif defined(USE_ETHERNET)
+ serial_interface.begin(TCP_PORT);
+ Serial.printf("TCP server started on port %d\n", TCP_PORT);
#elif defined(BLE_PIN_CODE)
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
#elif defined(SERIAL_RX)
@@ -220,9 +235,14 @@ void setup() {
void loop() {
the_mesh.loop();
+ #if defined(ESP32) && defined(TCP_CONSOLE_PORT) && defined(ADMIN_PASSWORD)
+ tcp_console.loop(the_mesh);
+ #endif
+
sensors.loop();
-#ifdef DISPLAY_CLASS
- ui_task.loop();
-#endif
+ #ifdef DISPLAY_CLASS
+ ui_task.loop();
+ #endif
+
rtc_clock.tick();
}
diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp
index 265532be0b..da264d5682 100644
--- a/examples/companion_radio/ui-new/UITask.cpp
+++ b/examples/companion_radio/ui-new/UITask.cpp
@@ -752,8 +752,8 @@ void UITask::loop() {
}
#endif
#if defined(PIN_USER_BTN_ANA)
- if (abs(millis() - _analogue_pin_read_millis) > 10) {
- ev = analog_btn.check();
+ if ((long)(millis() - _analogue_pin_read_millis) > 10) {
+ int ev = analog_btn.check();
if (ev == BUTTON_EVENT_CLICK) {
c = checkDisplayOn(KEY_NEXT);
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp
index d226d1fa76..08c914bc66 100644
--- a/examples/simple_repeater/main.cpp
+++ b/examples/simple_repeater/main.cpp
@@ -3,6 +3,11 @@
#include "MyMesh.h"
+#if defined(TCP_CONSOLE_PORT)
+ #include
+ TCPConsole tcp_console(nullptr); // prefs set in setup()
+#endif
+
#ifdef DISPLAY_CLASS
#include "UITask.h"
static UITask ui_task(display);
@@ -29,6 +34,10 @@ void setup() {
board.begin();
+ #if defined(TCP_CONSOLE_PORT)
+ tcp_console.begin();
+ #endif
+
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
// give some extra time for serial to settle so
// boot debug messages can be seen on terminal
@@ -90,6 +99,19 @@ void setup() {
the_mesh.begin(fs);
+the_mesh.begin(fs);
+
+#ifdef USE_ETHERNET
+ NodePrefs* prefs = the_mesh.getNodePrefs();
+ if (prefs->eth_ip != 0) {
+ board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet);
+ }
+#endif
+
+#if defined(TCP_CONSOLE_PORT)
+ tcp_console.setPrefs(the_mesh.getNodePrefs());
+#endif
+
#ifdef DISPLAY_CLASS
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
@@ -128,6 +150,10 @@ void loop() {
}
the_mesh.loop();
+ #if defined(ESP32) && defined(TCP_CONSOLE_PORT)
+ tcp_console.loop(the_mesh);
+ #endif
+
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp
index 825fb007d5..3535f4136a 100644
--- a/examples/simple_room_server/main.cpp
+++ b/examples/simple_room_server/main.cpp
@@ -3,6 +3,11 @@
#include "MyMesh.h"
+#if defined(TCP_CONSOLE_PORT)
+ #include
+ TCPConsole tcp_console(nullptr); // prefs set in setup()
+#endif
+
#ifdef DISPLAY_CLASS
#include "UITask.h"
static UITask ui_task(display);
@@ -24,6 +29,10 @@ void setup() {
board.begin();
+ #if defined(ESP32) && defined(TCP_CONSOLE_PORT)
+ tcp_console.begin();
+ #endif
+
#ifdef DISPLAY_CLASS
if (display.begin()) {
display.startFrame();
@@ -72,6 +81,17 @@ void setup() {
the_mesh.begin(fs);
+#ifdef USE_ETHERNET
+ NodePrefs* prefs = the_mesh.getNodePrefs();
+ if (prefs->eth_ip != 0) {
+ board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet);
+ }
+#endif
+
+ #if defined(TCP_CONSOLE_PORT)
+ tcp_console.setPrefs(the_mesh.getNodePrefs());
+#endif
+
#ifdef DISPLAY_CLASS
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
@@ -108,9 +128,13 @@ void loop() {
}
the_mesh.loop();
+ #if defined(ESP32) && defined(TCP_CONSOLE_PORT)
+ tcp_console.loop(the_mesh);
+ #endif
+
sensors.loop();
-#ifdef DISPLAY_CLASS
- ui_task.loop();
-#endif
+ #ifdef DISPLAY_CLASS
+ ui_task.loop();
+ #endif
rtc_clock.tick();
}
diff --git a/lib/ETHClass2/library.properties b/lib/ETHClass2/library.properties
new file mode 100644
index 0000000000..73e9002a70
--- /dev/null
+++ b/lib/ETHClass2/library.properties
@@ -0,0 +1,9 @@
+name=ETHClass2
+version=1.0.0
+author=Lewis He
+maintainer=Lewis He
+sentence=Enables network connection (local and Internet) using the ESP32/ESP32S3 Ethernet.
+paragraph=With this library you can instantiate Servers, Clients and send/receive UDP packets through Ethernet. The IP address can be assigned statically or through a DHCP. The library can also manage DNS.
+category=Communication
+url=
+architectures=esp32
diff --git a/lib/ETHClass2/src/ETHClass2.cpp b/lib/ETHClass2/src/ETHClass2.cpp
new file mode 100644
index 0000000000..57c2cb7e0c
--- /dev/null
+++ b/lib/ETHClass2/src/ETHClass2.cpp
@@ -0,0 +1,1075 @@
+/*
+ ETH.h - espre ETH PHY support.
+ Based on WiFi.h from Arduino WiFi shield library.
+ Copyright (c) 2011-2014 Arduino. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+// Disable the automatic pin remapping of the API calls in this file
+#define ARDUINO_CORE_BUILD
+
+#include "ETHClass2.h"
+#include "esp_system.h"
+#include "esp_event.h"
+#include "esp_eth.h"
+#include "esp_eth_mac.h"
+#include "esp_eth_com.h"
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#if CONFIG_ETH_USE_ESP32_EMAC
+#include "soc/emac_ext_struct.h"
+#include "soc/rtc.h"
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+// #include "esp32-hal-periman.h"
+#include "lwip/err.h"
+#include "lwip/dns.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include "esp_netif_types.h"
+#include "esp_netif_defaults.h"
+#include "esp_eth_phy.h"
+
+#ifndef ETH_ADDR_LEN
+#define ETH_ADDR_LEN 6
+#endif
+
+
+#undef CONFIG_ETH_SPI_ETHERNET_DM9051
+
+extern void tcpipInit();
+extern void add_esp_interface_netif(esp_interface_t interface, esp_netif_t *esp_netif); /* from WiFiGeneric */
+
+
+ETHClass2::ETHClass2(uint8_t eth_index)
+ : _eth_started(false)
+ , _eth_handle(NULL)
+ , _esp_netif(NULL)
+ , _eth_index(eth_index)
+ , _phy_type(ETH_PHY_MAX)
+#if ETH_SPI_SUPPORTS_CUSTOM
+ , _spi(NULL)
+#endif
+ , _spi_freq_mhz(20)
+ , _pin_cs(-1)
+ , _pin_irq(-1)
+ , _pin_rst(-1)
+ , _pin_sck(-1)
+ , _pin_miso(-1)
+ , _pin_mosi(-1)
+#if CONFIG_ETH_USE_ESP32_EMAC
+ , _pin_mcd(-1)
+ , _pin_mdio(-1)
+ , _pin_power(-1)
+ , _pin_rmii_clock(-1)
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+{}
+
+ETHClass2::~ETHClass2()
+{}
+
+bool ETHClass2::ethDetachBus(void *bus_pointer)
+{
+ ETHClass2 *bus = (ETHClass2 *) bus_pointer;
+ if (bus->_eth_started) {
+ bus->end();
+ }
+ return true;
+}
+
+#if CONFIG_ETH_USE_ESP32_EMAC
+bool ETHClass2::begin(eth_phy_type_t type, uint8_t phy_addr, int mdc, int mdio, int power, eth_clock_mode_t clock_mode)
+{
+ esp_err_t ret = ESP_OK;
+ if (_esp_netif != NULL) {
+ return true;
+ }
+
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_RMII, ETHClass2::ethDetachBus);
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_CLK, ETHClass2::ethDetachBus);
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_MCD, ETHClass2::ethDetachBus);
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_MDIO, ETHClass2::ethDetachBus);
+ // if(power != -1){
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_PWR, ETHClass2::ethDetachBus);
+ // }
+
+ tcpipInit();
+
+ eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+ mac_config.clock_config.rmii.clock_mode = (clock_mode) ? EMAC_CLK_OUT : EMAC_CLK_EXT_IN;
+ mac_config.clock_config.rmii.clock_gpio = (1 == clock_mode) ? EMAC_APPL_CLK_OUT_GPIO : (2 == clock_mode) ? EMAC_CLK_OUT_GPIO : (3 == clock_mode) ? EMAC_CLK_OUT_180_GPIO : EMAC_CLK_IN_GPIO;
+ mac_config.smi_mdc_gpio_num = digitalPinToGPIONumber(mdc);
+ mac_config.smi_mdio_gpio_num = digitalPinToGPIONumber(mdio);
+
+ _pin_mcd = digitalPinToGPIONumber(mdc);
+ _pin_mdio = digitalPinToGPIONumber(mdio);
+ _pin_rmii_clock = mac_config.clock_config.rmii.clock_gpio;
+ _pin_power = digitalPinToGPIONumber(power);
+
+ // if(!perimanClearPinBus(_pin_rmii_clock)){ return false; }
+ // if(!perimanClearPinBus(_pin_mcd)){ return false; }
+ // if(!perimanClearPinBus(_pin_mdio)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_TX_EN)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_TX0)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_TX1)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_RX0)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_RX1_EN)){ return false; }
+ // if(!perimanClearPinBus(ETH_RMII_CRS_DV)){ return false; }
+ // if(_pin_power != -1){
+ // if(!perimanClearPinBus(_pin_power)){ return false; }
+ // }
+
+ eth_mac_config_t eth_mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_mac_config.sw_reset_timeout_ms = 1000;
+
+ esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
+ if (mac == NULL) {
+ log_e("esp_eth_mac_new_esp32 failed");
+ return false;
+ }
+
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+ phy_config.phy_addr = phy_addr;
+ phy_config.reset_gpio_num = _pin_power;
+
+ esp_eth_phy_t *phy = NULL;
+ switch (type) {
+ case ETH_PHY_LAN8720:
+ phy = esp_eth_phy_new_lan87xx(&phy_config);
+ break;
+ case ETH_PHY_TLK110:
+ // phy = esp_eth_phy_new_ip101(&phy_config);
+ break;
+ case ETH_PHY_RTL8201:
+ phy = esp_eth_phy_new_rtl8201(&phy_config);
+ break;
+ case ETH_PHY_DP83848:
+ // phy = esp_eth_phy_new_dp83848(&phy_config);
+ break;
+ case ETH_PHY_KSZ8041:
+ // phy = esp_eth_phy_new_ksz80xx(&phy_config);
+ break;
+ case ETH_PHY_KSZ8081:
+ // phy = esp_eth_phy_new_ksz80xx(&phy_config);
+ break;
+ default:
+ log_e("Unsupported PHY %d", type);
+ break;
+ }
+ if (phy == NULL) {
+ log_e("esp_eth_phy_new failed");
+ return false;
+ }
+
+ _eth_handle = NULL;
+ esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
+ ret = esp_eth_driver_install(ð_config, &_eth_handle);
+ if (ret != ESP_OK) {
+ log_e("SPI Ethernet driver install failed: %d", ret);
+ return false;
+ }
+ if (_eth_handle == NULL) {
+ log_e("esp_eth_driver_install failed! eth_handle is NULL");
+ return false;
+ }
+
+ esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
+
+ // Use ESP_NETIF_INHERENT_DEFAULT_ETH when multiple Ethernet interfaces are used and so you need to modify
+ // esp-netif configuration parameters for each interface (name, priority, etc.).
+ char if_key_str[10];
+ char if_desc_str[10];
+ if (_eth_index == 0) {
+ strcpy(if_key_str, "ETH_DEF");
+ strcpy(if_desc_str, "eth");
+ } else {
+ char num_str[3];
+ itoa(_eth_index, num_str, 10);
+ strcat(strcpy(if_key_str, "ETH_"), num_str);
+ strcat(strcpy(if_desc_str, "eth"), num_str);
+ }
+
+ esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
+ esp_netif_config.if_key = if_key_str;
+ esp_netif_config.if_desc = if_desc_str;
+ esp_netif_config.route_prio -= _eth_index * 5;
+
+ cfg.base = &esp_netif_config;
+
+ _esp_netif = esp_netif_new(&cfg);
+
+ /* attach Ethernet driver to TCP/IP stack */
+ ret = esp_netif_attach(_esp_netif, esp_eth_new_netif_glue(_eth_handle));
+ if (ret != ESP_OK) {
+ log_e("esp_netif_attach failed: %d", ret);
+ return false;
+ }
+
+ /* attach to WiFiGeneric to receive events */
+ add_esp_interface_netif(ESP_IF_ETH, _esp_netif);
+
+ ret = esp_eth_start(_eth_handle);
+ if (ret != ESP_OK) {
+ log_e("esp_eth_start failed: %d", ret);
+ return false;
+ }
+ _eth_started = true;
+
+ // if(!perimanSetPinBus(_pin_rmii_clock, ESP32_BUS_TYPE_ETHERNET_CLK, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(_pin_mcd, ESP32_BUS_TYPE_ETHERNET_MCD, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(_pin_mdio, ESP32_BUS_TYPE_ETHERNET_MDIO, (void *)(this), -1, -1)){ goto err; }
+
+ // if(!perimanSetPinBus(ETH_RMII_TX_EN, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(ETH_RMII_TX0, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(ETH_RMII_TX1, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(ETH_RMII_RX0, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(ETH_RMII_RX1_EN, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+ // if(!perimanSetPinBus(ETH_RMII_CRS_DV, ESP32_BUS_TYPE_ETHERNET_RMII, (void *)(this), -1, -1)){ goto err; }
+
+ // if(_pin_power != -1){
+ // if(!perimanSetPinBus(_pin_power, ESP32_BUS_TYPE_ETHERNET_PWR, (void *)(this), -1, -1)){ goto err; }
+ // }
+ // holds a few milliseconds to let DHCP start and enter into a good state
+ // FIX ME -- adresses issue https://github.com/espressif/arduino-esp32/issues/5733
+ delay(50);
+
+ return true;
+
+err:
+ log_e("Failed to set all pins bus to ETHERNET");
+ ETHClass2::ethDetachBus((void *)(this));
+ return false;
+}
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+static void *_eth_spi_init(const void *ctx)
+{
+ return (void *)ctx;
+}
+
+static esp_err_t _eth_spi_deinit(void *ctx)
+{
+ return ESP_OK;
+}
+
+esp_err_t ETHClass2::_eth_spi_read(void *ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t data_len)
+{
+ return ((ETHClass2 *)ctx)->eth_spi_read(cmd, addr, data, data_len);
+}
+
+esp_err_t ETHClass2::_eth_spi_write(void *ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len)
+{
+ return ((ETHClass2 *)ctx)->eth_spi_write(cmd, addr, data, data_len);
+}
+
+esp_err_t ETHClass2::eth_spi_read(uint32_t cmd, uint32_t addr, void *data, uint32_t data_len)
+{
+ if (_spi == NULL) {
+ return ESP_FAIL;
+ }
+ // log_i(" 0x%04lx 0x%04lx %lu", cmd, addr, data_len);
+ _spi->beginTransaction(SPISettings(_spi_freq_mhz * 1000 * 1000, MSBFIRST, SPI_MODE0));
+ digitalWrite(_pin_cs, LOW);
+
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+ if (_phy_type == ETH_PHY_DM9051) {
+ _spi->write(((cmd & 0x01) << 7) | (addr & 0x7F));
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_W5500
+ if (_phy_type == ETH_PHY_W5500) {
+ _spi->write16(cmd);
+ _spi->write(addr);
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL
+ if (_phy_type == ETH_PHY_KSZ8851) {
+ if (cmd > 1) {
+ _spi->write(cmd << 6 | addr);
+ } else {
+ _spi->write16(cmd << 14 | addr);
+ }
+ } else
+#endif
+ {
+ log_e("Unsupported PHY module: %d", _phy_type);
+ digitalWrite(_pin_cs, HIGH);
+ _spi->endTransaction();
+ return ESP_FAIL;
+ }
+ _spi->transferBytes(NULL, (uint8_t *)data, data_len);
+
+ digitalWrite(_pin_cs, HIGH);
+ _spi->endTransaction();
+ return ESP_OK;
+}
+
+esp_err_t ETHClass2::eth_spi_write(uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len)
+{
+ if (_spi == NULL) {
+ return ESP_FAIL;
+ }
+ // log_i("0x%04lx 0x%04lx %lu", cmd, addr, data_len);
+ _spi->beginTransaction(SPISettings(_spi_freq_mhz * 1000 * 1000, MSBFIRST, SPI_MODE0));
+ digitalWrite(_pin_cs, LOW);
+
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+ if (_phy_type == ETH_PHY_DM9051) {
+ _spi->write(((cmd & 0x01) << 7) | (addr & 0x7F));
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_W5500
+ if (_phy_type == ETH_PHY_W5500) {
+ _spi->write16(cmd);
+ _spi->write(addr);
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL
+ if (_phy_type == ETH_PHY_KSZ8851) {
+ if (cmd > 1) {
+ _spi->write(cmd << 6 | addr);
+ } else {
+ _spi->write16(cmd << 14 | addr);
+ }
+ } else
+#endif
+ {
+ log_e("Unsupported PHY module: %d", _phy_type);
+ digitalWrite(_pin_cs, HIGH);
+ _spi->endTransaction();
+ return ESP_FAIL;
+ }
+ _spi->writeBytes((const uint8_t *)data, data_len);
+
+ digitalWrite(_pin_cs, HIGH);
+ _spi->endTransaction();
+ return ESP_OK;
+}
+#endif
+
+bool ETHClass2::beginSPI(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst,
+#if ETH_SPI_SUPPORTS_CUSTOM
+ SPIClass *spi,
+#endif
+ int sck, int miso, int mosi, spi_host_device_t spi_host, uint8_t spi_freq_mhz)
+{
+ esp_err_t ret = ESP_OK;
+
+ if (_eth_started || _esp_netif != NULL || _eth_handle != NULL) {
+ log_w("ETH Already Started");
+ return true;
+ }
+ if (cs < 0 || irq < 0) {
+ log_e("CS and IRQ pins must be defined!");
+ return false;
+ }
+
+ // perimanSetBusDeinit(ESP32_BUS_TYPE_ETHERNET_SPI, ETHClass2::ethDetachBus);
+
+ // if(_pin_cs != -1){
+ // if(!perimanClearPinBus(_pin_cs)){ return false; }
+ // }
+ // if(_pin_rst != -1){
+ // if(!perimanClearPinBus(_pin_rst)){ return false; }
+ // }
+ // if(_pin_irq != -1){
+ // if(!perimanClearPinBus(_pin_irq)){ return false; }
+ // }
+ // if(_pin_sck != -1){
+ // if(!perimanClearPinBus(_pin_sck)){ return false; }
+ // }
+ // if(_pin_miso != -1){
+ // if(!perimanClearPinBus(_pin_miso)){ return false; }
+ // }
+ // if(_pin_mosi != -1){
+ // if(!perimanClearPinBus(_pin_mosi)){ return false; }
+ // }
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+ _spi = spi;
+#endif
+ if (spi_freq_mhz) {
+ _spi_freq_mhz = spi_freq_mhz;
+ }
+ _phy_type = type;
+ _pin_cs = digitalPinToGPIONumber(cs);
+ _pin_irq = digitalPinToGPIONumber(irq);
+ _pin_rst = digitalPinToGPIONumber(rst);
+ _pin_sck = digitalPinToGPIONumber(sck);
+ _pin_miso = digitalPinToGPIONumber(miso);
+ _pin_mosi = digitalPinToGPIONumber(mosi);
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+ if (_spi != NULL) {
+ pinMode(_pin_cs, OUTPUT);
+ digitalWrite(_pin_cs, HIGH);
+ // perimanSetPinBusExtraType(_pin_cs, "ETH_CS");
+ }
+#endif
+
+
+ tcpipInit();
+
+ // Install GPIO ISR handler to be able to service SPI Eth modules interrupts
+ ret = gpio_install_isr_service(0);
+ if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
+ log_e("GPIO ISR handler install failed: %d", ret);
+ return false;
+ }
+
+ // Init common MAC and PHY configs to default
+ eth_mac_config_t eth_mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+
+ // Update PHY config based on board specific configuration
+ phy_config.phy_addr = phy_addr;
+ phy_config.reset_gpio_num = _pin_rst;
+
+ // Init SPI bus
+ spi_device_handle_t spi_handle = {0};
+ spi_bus_config_t buscfg = {0};
+ buscfg.miso_io_num = _pin_miso;
+ buscfg.mosi_io_num = _pin_mosi;
+ buscfg.sclk_io_num = _pin_sck;
+ buscfg.quadwp_io_num = -1;
+ buscfg.quadhd_io_num = -1;
+ ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO);
+ if (ret != ESP_OK) {
+ log_e("spi_bus_initialize failed");
+ return false;
+ }
+
+ // Configure SPI interface for specific SPI module
+ spi_device_interface_config_t spi_devcfg = {
+ .command_bits = 16, // Actually it's the address phase in W5500 SPI frame
+ .address_bits = 8, // Actually it's the control phase in W5500 SPI frame
+ .mode = 0,
+ .clock_speed_hz = _spi_freq_mhz * 1000 * 1000,
+ .queue_size = 20
+ };
+ // Set SPI module Chip Select GPIO
+ spi_devcfg.spics_io_num = _pin_cs;
+
+ ret = spi_bus_add_device(spi_host, &spi_devcfg, &spi_handle);
+ if (ret != ESP_OK) {
+ log_e("spi_bus_add_device failed");
+ return false;
+ }
+
+ esp_eth_mac_t *mac = NULL;
+ esp_eth_phy_t *phy = NULL;
+#if CONFIG_ETH_SPI_ETHERNET_W5500
+ if (type == ETH_PHY_W5500) {
+
+ eth_w5500_config_t mac_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
+
+ mac_config.int_gpio_num = _pin_irq;
+#if ETH_SPI_SUPPORTS_CUSTOM
+ if (_spi != NULL) {
+ mac_config.custom_spi_driver.config = this;
+ mac_config.custom_spi_driver.init = _eth_spi_init;
+ mac_config.custom_spi_driver.deinit = _eth_spi_deinit;
+ mac_config.custom_spi_driver.read = _eth_spi_read;
+ mac_config.custom_spi_driver.write = _eth_spi_write;
+ }
+#endif
+ mac = esp_eth_mac_new_w5500(&mac_config, ð_mac_config);
+ phy = esp_eth_phy_new_w5500(&phy_config);
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+ if (type == ETH_PHY_DM9051) {
+ eth_dm9051_config_t mac_config = ETH_DM9051_DEFAULT_CONFIG(spi_host, &spi_devcfg);
+ mac_config.int_gpio_num = _pin_irq;
+#if ETH_SPI_SUPPORTS_CUSTOM
+ if (_spi != NULL) {
+ mac_config.custom_spi_driver.config = this;
+ mac_config.custom_spi_driver.init = _eth_spi_init;
+ mac_config.custom_spi_driver.deinit = _eth_spi_deinit;
+ mac_config.custom_spi_driver.read = _eth_spi_read;
+ mac_config.custom_spi_driver.write = _eth_spi_write;
+ }
+#endif
+ mac = esp_eth_mac_new_dm9051(&mac_config, ð_mac_config);
+ phy = esp_eth_phy_new_dm9051(&phy_config);
+ } else
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL
+ if (type == ETH_PHY_KSZ8851) {
+ eth_ksz8851snl_config_t mac_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_host, &spi_devcfg);
+ mac_config.int_gpio_num = _pin_irq;
+#if ETH_SPI_SUPPORTS_CUSTOM
+ if (_spi != NULL) {
+ mac_config.custom_spi_driver.config = this;
+ mac_config.custom_spi_driver.init = _eth_spi_init;
+ mac_config.custom_spi_driver.deinit = _eth_spi_deinit;
+ mac_config.custom_spi_driver.read = _eth_spi_read;
+ mac_config.custom_spi_driver.write = _eth_spi_write;
+ }
+#endif
+ mac = esp_eth_mac_new_ksz8851snl(&mac_config, ð_mac_config);
+ phy = esp_eth_phy_new_ksz8851snl(&phy_config);
+ } else
+#endif
+ {
+ log_e("Unsupported PHY module: %d", (int)type);
+ return false;
+ }
+
+ // Init Ethernet driver to default and install it
+ esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
+ ret = esp_eth_driver_install(ð_config, &_eth_handle);
+ if (ret != ESP_OK) {
+ log_e("SPI Ethernet driver install failed: %d", ret);
+ return false;
+ }
+ if (_eth_handle == NULL) {
+ log_e("esp_eth_driver_install failed! eth_handle is NULL");
+ return false;
+ }
+
+ // Derive a new MAC address for this interface
+ uint8_t base_mac_addr[ETH_ADDR_LEN];
+ ret = esp_efuse_mac_get_default(base_mac_addr);
+ if (ret != ESP_OK) {
+ log_e("Get EFUSE MAC failed: %d", ret);
+ return false;
+ }
+ uint8_t mac_addr[ETH_ADDR_LEN];
+ base_mac_addr[ETH_ADDR_LEN - 1] += _eth_index+1; // Increment by the ETH number, and we start at 0 so add 1 more
+ esp_derive_local_mac(mac_addr, base_mac_addr);
+
+ ret = esp_eth_ioctl(_eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr);
+ if (ret != ESP_OK) {
+ log_e("SPI Ethernet MAC address config failed: %d", ret);
+ return false;
+ }
+
+ // Use ESP_NETIF_DEFAULT_ETH when just one Ethernet interface is used and you don't need to modify
+ // default esp-netif configuration parameters.
+ esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
+
+ // Use ESP_NETIF_INHERENT_DEFAULT_ETH when multiple Ethernet interfaces are used and so you need to modify
+ // esp-netif configuration parameters for each interface (name, priority, etc.).
+ char if_key_str[10];
+ char if_desc_str[10];
+ if (_eth_index == 0) {
+ strcpy(if_key_str, "ETH_DEF");
+ strcpy(if_desc_str, "eth");
+ } else {
+ char num_str[3];
+ itoa(_eth_index, num_str, 10);
+ strcat(strcpy(if_key_str, "ETH_"), num_str);
+ strcat(strcpy(if_desc_str, "eth"), num_str);
+ }
+
+ esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
+ esp_netif_config.if_key = if_key_str;
+ esp_netif_config.if_desc = if_desc_str;
+ esp_netif_config.route_prio -= _eth_index * 5;
+
+ cfg.base = &esp_netif_config;
+
+ _esp_netif = esp_netif_new(&cfg);
+ if (_esp_netif == NULL) {
+ log_e("esp_netif_new failed");
+ return false;
+ }
+ // Attach Ethernet driver to TCP/IP stack
+ esp_eth_netif_glue_handle_t new_netif_glue = esp_eth_new_netif_glue(_eth_handle);
+ if (new_netif_glue == NULL) {
+ log_e("esp_eth_new_netif_glue failed");
+ return false;
+ }
+
+ ret = esp_netif_attach(_esp_netif, new_netif_glue);
+ if (ret != ESP_OK) {
+ log_e("esp_netif_attach failed: %d", ret);
+ return false;
+ }
+
+ // attach to WiFiGeneric to receive events
+ add_esp_interface_netif(ESP_IF_ETH, _esp_netif);
+
+ // Start Ethernet driver state machine
+ ret = esp_eth_start(_eth_handle);
+ if (ret != ESP_OK) {
+ log_e("esp_eth_start failed: %d", ret);
+ return false;
+ }
+
+ _eth_started = true;
+
+ // If Arduino's SPI is used, cs pin is in GPIO mode
+#if ETH_SPI_SUPPORTS_CUSTOM
+ if (_spi == NULL) {
+#endif
+ // if(!perimanSetPinBus(_pin_cs, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+#if ETH_SPI_SUPPORTS_CUSTOM
+ }
+#endif
+ // if(!perimanSetPinBus(_pin_irq, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+
+ // if(_pin_sck != -1){
+ // if(!perimanSetPinBus(_pin_sck, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+ // }
+ // if(_pin_miso != -1){
+ // if(!perimanSetPinBus(_pin_miso, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+ // }
+ // if(_pin_mosi != -1){
+ // if(!perimanSetPinBus(_pin_mosi, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+ // }
+ // if(_pin_rst != -1){
+ // if(!perimanSetPinBus(_pin_rst, ESP32_BUS_TYPE_ETHERNET_SPI, (void *)(this), -1, -1)){ goto err; }
+ // }
+
+ return true;
+
+err:
+ log_e("Failed to set all pins bus to ETHERNET");
+ ETHClass2::ethDetachBus((void *)(this));
+ return false;
+}
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+bool ETHClass2::begin(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst, SPIClass &spi, uint8_t spi_freq_mhz)
+{
+
+ return beginSPI(type, phy_addr, cs, irq, rst, &spi, -1, -1, -1, SPI2_HOST, spi_freq_mhz);
+}
+#endif
+
+bool ETHClass2::begin(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst, spi_host_device_t spi_host, int sck, int miso, int mosi, uint8_t spi_freq_mhz)
+{
+
+ return beginSPI(type, phy_addr, cs, irq, rst,
+#if ETH_SPI_SUPPORTS_CUSTOM
+ NULL,
+#endif
+ sck, miso, mosi, spi_host, spi_freq_mhz);
+}
+
+void ETHClass2::end(void)
+{
+ _eth_started = false;
+
+ if (_esp_netif != NULL) {
+ esp_netif_destroy(_esp_netif);
+ _esp_netif = NULL;
+ }
+
+ if (_eth_handle != NULL) {
+ if (esp_eth_stop(_eth_handle) != ESP_OK) {
+ log_e("Failed to stop Ethernet");
+ return;
+ }
+ if (esp_eth_driver_uninstall(_eth_handle) != ESP_OK) {
+ log_e("Failed to stop Ethernet");
+ return;
+ }
+ _eth_handle = NULL;
+ }
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+ _spi = NULL;
+#endif
+
+#if CONFIG_ETH_USE_ESP32_EMAC
+ if (_pin_rmii_clock != -1 && _pin_mcd != -1 && _pin_mdio != -1) {
+ // perimanClearPinBus(_pin_rmii_clock);
+ // perimanClearPinBus(_pin_mcd);
+ // perimanClearPinBus(_pin_mdio);
+
+ // perimanClearPinBus(ETH_RMII_TX_EN);
+ // perimanClearPinBus(ETH_RMII_TX0);
+ // perimanClearPinBus(ETH_RMII_TX1);
+ // perimanClearPinBus(ETH_RMII_RX0);
+ // perimanClearPinBus(ETH_RMII_RX1_EN);
+ // perimanClearPinBus(ETH_RMII_CRS_DV);
+
+ _pin_rmii_clock = -1;
+ _pin_mcd = -1;
+ _pin_mdio = -1;
+ }
+
+ if (_pin_power != -1) {
+ // perimanClearPinBus(_pin_power);
+ _pin_power = -1;
+ }
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+ if (_pin_cs != -1) {
+ // perimanClearPinBus(_pin_cs);
+ _pin_cs = -1;
+ }
+ if (_pin_irq != -1) {
+ // perimanClearPinBus(_pin_irq);
+ _pin_irq = -1;
+ }
+ if (_pin_sck != -1) {
+ // perimanClearPinBus(_pin_sck);
+ _pin_sck = -1;
+ }
+ if (_pin_miso != -1) {
+ // perimanClearPinBus(_pin_miso);
+ _pin_miso = -1;
+ }
+ if (_pin_mosi != -1) {
+ // perimanClearPinBus(_pin_mosi);
+ _pin_mosi = -1;
+ }
+ if (_pin_rst != -1) {
+ // perimanClearPinBus(_pin_rst);
+ _pin_rst = -1;
+ }
+}
+
+bool ETHClass2::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2)
+{
+ if (_esp_netif == NULL) {
+ return false;
+ }
+ esp_err_t err = ESP_OK;
+ esp_netif_ip_info_t info;
+ esp_netif_dns_info_t d1;
+ esp_netif_dns_info_t d2;
+ d1.ip.type = IPADDR_TYPE_V4;
+ d2.ip.type = IPADDR_TYPE_V4;
+
+ if (static_cast(local_ip) != 0) {
+ info.ip.addr = static_cast(local_ip);
+ info.gw.addr = static_cast(gateway);
+ info.netmask.addr = static_cast(subnet);
+ d1.ip.u_addr.ip4.addr = static_cast(dns1);
+ d2.ip.u_addr.ip4.addr = static_cast(dns2);
+ } else {
+ info.ip.addr = 0;
+ info.gw.addr = 0;
+ info.netmask.addr = 0;
+ d1.ip.u_addr.ip4.addr = 0;
+ d2.ip.u_addr.ip4.addr = 0;
+ }
+
+ // Stop DHCPC
+ err = esp_netif_dhcpc_stop(_esp_netif);
+ if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
+ log_e("DHCP could not be stopped! Error: %d", err);
+ return false;
+ }
+
+ // Set IPv4, Netmask, Gateway
+ err = esp_netif_set_ip_info(_esp_netif, &info);
+ if (err != ERR_OK) {
+ log_e("ETH IP could not be configured! Error: %d", err);
+ return false;
+ }
+
+ // Set DNS1-Server
+ esp_netif_set_dns_info(_esp_netif, ESP_NETIF_DNS_MAIN, &d1);
+
+ // Set DNS2-Server
+ esp_netif_set_dns_info(_esp_netif, ESP_NETIF_DNS_BACKUP, &d2);
+
+ // Start DHCPC if static IP was set
+ if (info.ip.addr == 0) {
+ err = esp_netif_dhcpc_start(_esp_netif);
+ if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
+ log_w("DHCP could not be started! Error: %d", err);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+IPAddress ETHClass2::localIP()
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return IPAddress();
+ }
+ return IPAddress(ip.ip.addr);
+}
+
+IPAddress ETHClass2::subnetMask()
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return IPAddress();
+ }
+ return IPAddress(ip.netmask.addr);
+}
+
+IPAddress ETHClass2::gatewayIP()
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return IPAddress();
+ }
+ return IPAddress(ip.gw.addr);
+}
+
+IPAddress ETHClass2::dnsIP(uint8_t dns_no)
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_dns_info_t d;
+ if (esp_netif_get_dns_info(_esp_netif, dns_no ? ESP_NETIF_DNS_BACKUP : ESP_NETIF_DNS_MAIN, &d) != ESP_OK) {
+ return IPAddress();
+ }
+ return IPAddress(d.ip.u_addr.ip4.addr);
+}
+
+IPAddress ETHClass2::broadcastIP()
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return IPAddress();
+ }
+ return WiFiGenericClass::calculateBroadcast(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr));
+}
+
+IPAddress ETHClass2::networkID()
+{
+ if (_esp_netif == NULL) {
+ return IPAddress();
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return IPAddress();
+ }
+ return WiFiGenericClass::calculateNetworkID(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr));
+}
+
+uint8_t ETHClass2::subnetCIDR()
+{
+ if (_esp_netif == NULL) {
+ return (uint8_t)0;
+ }
+ esp_netif_ip_info_t ip;
+ if (esp_netif_get_ip_info(_esp_netif, &ip)) {
+ return (uint8_t)0;
+ }
+ return WiFiGenericClass::calculateSubnetCIDR(IPAddress(ip.netmask.addr));
+}
+
+const char *ETHClass2::getHostname()
+{
+ if (_esp_netif == NULL) {
+ return "";
+ }
+ const char *hostname;
+ if (esp_netif_get_hostname(_esp_netif, &hostname)) {
+ return NULL;
+ }
+ return hostname;
+}
+
+bool ETHClass2::setHostname(const char *hostname)
+{
+ if (_esp_netif == NULL) {
+ return false;
+ }
+ return esp_netif_set_hostname(_esp_netif, hostname) == 0;
+}
+
+bool ETHClass2::enableIpV6()
+{
+ if (_esp_netif == NULL) {
+ return false;
+ }
+ return esp_netif_create_ip6_linklocal(_esp_netif) == 0;
+}
+
+IPv6Address ETHClass2::localIPv6()
+{
+ if (_esp_netif == NULL) {
+ return IPv6Address();
+ }
+ static esp_ip6_addr_t addr;
+ if (esp_netif_get_ip6_linklocal(_esp_netif, &addr)) {
+ return IPv6Address();
+ }
+ return IPv6Address(addr.addr);
+}
+
+const char *ETHClass2::ifkey(void)
+{
+ if (_esp_netif == NULL) {
+ return "";
+ }
+ return esp_netif_get_ifkey(_esp_netif);
+}
+
+const char *ETHClass2::desc(void)
+{
+ if (_esp_netif == NULL) {
+ return "";
+ }
+ return esp_netif_get_desc(_esp_netif);
+}
+
+String ETHClass2::impl_name(void)
+{
+ if (_esp_netif == NULL) {
+ return String("");
+ }
+ char netif_name[8];
+ esp_err_t err = esp_netif_get_netif_impl_name(_esp_netif, netif_name);
+ if (err != ESP_OK) {
+ log_e("Failed to get netif impl_name: %d", err);
+ return String("");
+ }
+ return String(netif_name);
+}
+
+bool ETHClass2::connected()
+{
+ return WiFiGenericClass::getStatusBits() & ETH_CONNECTED_BIT;
+}
+
+bool ETHClass2::hasIP()
+{
+ return WiFiGenericClass::getStatusBits() & ETH_HAS_IP_BIT;
+}
+
+bool ETHClass2::linkUp()
+{
+ if (_esp_netif == NULL) {
+ return false;
+ }
+ return esp_netif_is_netif_up(_esp_netif);
+}
+
+bool ETHClass2::fullDuplex()
+{
+ if (_eth_handle == NULL) {
+ return false;
+ }
+ eth_duplex_t link_duplex;
+ esp_eth_ioctl(_eth_handle, ETH_CMD_G_DUPLEX_MODE, &link_duplex);
+ return (link_duplex == ETH_DUPLEX_FULL);
+}
+
+bool ETHClass2::autoNegotiation()
+{
+ if (_eth_handle == NULL) {
+ return false;
+ }
+ bool auto_nego = false;;
+ // esp_eth_ioctl(_eth_handle, ETH_CMD_G_AUTONEGO, &auto_nego);
+ return auto_nego;
+}
+
+uint32_t ETHClass2::phyAddr()
+{
+ if (_eth_handle == NULL) {
+ return 0;
+ }
+ uint32_t phy_addr;
+ esp_eth_ioctl(_eth_handle, ETH_CMD_G_PHY_ADDR, &phy_addr);
+ return phy_addr;
+}
+
+uint8_t ETHClass2::linkSpeed()
+{
+ if (_eth_handle == NULL) {
+ return 0;
+ }
+ eth_speed_t link_speed;
+ esp_eth_ioctl(_eth_handle, ETH_CMD_G_SPEED, &link_speed);
+ return (link_speed == ETH_SPEED_10M) ? 10 : 100;
+}
+
+uint8_t *ETHClass2::macAddress(uint8_t *mac)
+{
+ if (_eth_handle == NULL) {
+ return NULL;
+ }
+ if (!mac) {
+ return NULL;
+ }
+ esp_eth_ioctl(_eth_handle, ETH_CMD_G_MAC_ADDR, mac);
+ return mac;
+}
+
+String ETHClass2::macAddress(void)
+{
+ uint8_t mac[6] = {0, 0, 0, 0, 0, 0};
+ char macStr[18] = { 0 };
+ macAddress(mac);
+ sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ return String(macStr);
+}
+
+void ETHClass2::printInfo(Print &out)
+{
+ out.print(desc());
+ out.print(":");
+ if (linkUp()) {
+ out.print(" ");
+
+ out.print(" ");
+ out.print("ether ");
+ out.print(macAddress());
+ out.printf(" phy 0x%lX", phyAddr());
+ out.println();
+
+ out.print(" ");
+ out.print("inet ");
+ out.print(localIP());
+ out.print(" netmask ");
+ out.print(subnetMask());
+ out.print(" broadcast ");
+ out.print(broadcastIP());
+ out.println();
+
+ out.print(" ");
+ out.print("gateway ");
+ out.print(gatewayIP());
+ out.print(" dns ");
+ out.print(dnsIP());
+ out.println();
+
+ out.println();
+}
+
+ETHClass2 ETH2;
diff --git a/lib/ETHClass2/src/ETHClass2.h b/lib/ETHClass2/src/ETHClass2.h
new file mode 100644
index 0000000000..8c4685fbd4
--- /dev/null
+++ b/lib/ETHClass2/src/ETHClass2.h
@@ -0,0 +1,219 @@
+/*
+ ETH.h - espre ETH PHY support.
+ Based on WiFi.h from Ardiono WiFi shield library.
+ Copyright (c) 2011-2014 Arduino. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _ETH_H_
+#define _ETH_H_
+
+//
+// Example configurations for pins_arduino.h to allow starting with ETH.begin();
+//
+
+// // Example RMII LAN8720 (Olimex, etc.)
+// #define ETH_PHY_TYPE ETH_PHY_LAN8720
+// #define ETH_PHY_ADDR 0
+// #define ETH_PHY_MDC 23
+// #define ETH_PHY_MDIO 18
+// #define ETH_PHY_POWER -1
+// #define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
+
+// // Example RMII ESP32_Ethernet_V4
+// #define ETH_PHY_TYPE ETH_PHY_TLK110
+// #define ETH_PHY_ADDR 1
+// #define ETH_PHY_MDC 23
+// #define ETH_PHY_MDIO 18
+// #define ETH_PHY_POWER -1
+// #define ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT
+
+// // Example SPI using ESP-IDF's driver
+// #define ETH_PHY_TYPE ETH_PHY_W5500
+// #define ETH_PHY_ADDR 1
+// #define ETH_PHY_CS 15
+// #define ETH_PHY_IRQ 4
+// #define ETH_PHY_RST 5
+// #define ETH_PHY_SPI_HOST SPI2_HOST
+// #define ETH_PHY_SPI_SCK 14
+// #define ETH_PHY_SPI_MISO 12
+// #define ETH_PHY_SPI_MOSI 13
+
+// // Example SPI using Arduino's driver
+// #define ETH_PHY_TYPE ETH_PHY_W5500
+// #define ETH_PHY_ADDR 1
+// #define ETH_PHY_CS 15
+// #define ETH_PHY_IRQ 4
+// #define ETH_PHY_RST 5
+// #define ETH_PHY_SPI SPI
+
+// This will be uncommented once custom SPI support is available in ESP-IDF
+#define ETH_SPI_SUPPORTS_CUSTOM 0
+#include "WiFi.h"
+#if ETH_SPI_SUPPORTS_CUSTOM
+#include "SPI.h"
+#endif
+#include "esp_system.h"
+#include "esp_eth.h"
+#include "esp_netif.h"
+#include "hal/spi_types.h"
+
+#if CONFIG_ETH_USE_ESP32_EMAC
+#define ETH_PHY_IP101 ETH_PHY_TLK110
+typedef enum { ETH_CLOCK_GPIO0_IN, ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT } eth_clock_mode_t;
+//Dedicated GPIOs for RMII
+#define ETH_RMII_TX_EN 21
+#define ETH_RMII_TX0 19
+#define ETH_RMII_TX1 22
+#define ETH_RMII_RX0 25
+#define ETH_RMII_RX1_EN 26
+#define ETH_RMII_CRS_DV 27
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+
+#ifndef ETH_PHY_SPI_FREQ_MHZ
+#define ETH_PHY_SPI_FREQ_MHZ 20
+#endif /* ETH_PHY_SPI_FREQ_MHZ */
+
+typedef enum {
+#if CONFIG_ETH_USE_ESP32_EMAC
+ ETH_PHY_LAN8720, ETH_PHY_TLK110, ETH_PHY_RTL8201, ETH_PHY_DP83848, ETH_PHY_KSZ8041, ETH_PHY_KSZ8081,
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+ ETH_PHY_DM9051,
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_W5500
+ ETH_PHY_W5500,
+#endif
+#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL
+ ETH_PHY_KSZ8851,
+#endif
+ ETH_PHY_MAX
+} eth_phy_type_t;
+
+class ETHClass2 {
+ public:
+ ETHClass2(uint8_t eth_index=0);
+ ~ETHClass2();
+
+#if CONFIG_ETH_USE_ESP32_EMAC
+ bool begin(eth_phy_type_t type, uint8_t phy_addr, int mdc, int mdio, int power, eth_clock_mode_t clk_mode);
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+#if ETH_SPI_SUPPORTS_CUSTOM
+ bool begin(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst, SPIClass &spi, uint8_t spi_freq_mhz=ETH_PHY_SPI_FREQ_MHZ);
+#endif
+ bool begin(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst, spi_host_device_t spi_host, int sck=-1, int miso=-1, int mosi=-1, uint8_t spi_freq_mhz=ETH_PHY_SPI_FREQ_MHZ);
+
+ bool begin(){
+#if defined(ETH_PHY_TYPE) && defined(ETH_PHY_ADDR)
+ #if defined(CONFIG_ETH_USE_ESP32_EMAC) && defined(ETH_PHY_POWER) && defined(ETH_PHY_MDC) && defined(ETH_PHY_MDIO) && defined(ETH_CLK_MODE)
+ return begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_POWER, ETH_CLK_MODE);
+ #elif defined(ETH_PHY_CS) && defined(ETH_PHY_IRQ) && defined(ETH_PHY_RST)
+ #if ETH_SPI_SUPPORTS_CUSTOM && defined(ETH_PHY_SPI)
+ return begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, ETH_PHY_SPI, ETH_PHY_SPI_FREQ_MHZ);
+ #elif defined(ETH_PHY_SPI_HOST) && defined(ETH_PHY_SPI_SCK) && defined(ETH_PHY_SPI_MISO) && defined(ETH_PHY_SPI_MOSI)
+ return begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, ETH_PHY_SPI_HOST, ETH_PHY_SPI_SCK, ETH_PHY_SPI_MISO, ETH_PHY_SPI_MOSI, ETH_PHY_SPI_FREQ_MHZ);
+ #endif
+ #endif
+#endif
+ return false;
+ }
+
+ void end();
+
+ // Netif APIs
+ esp_netif_t * netif(void){ return _esp_netif; }
+ bool config(IPAddress local_ip = (uint32_t)0x00000000, IPAddress gateway = (uint32_t)0x00000000, IPAddress subnet = (uint32_t)0x00000000, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000);
+ const char * getHostname();
+ bool setHostname(const char * hostname);
+ IPAddress localIP();
+ IPAddress subnetMask();
+ IPAddress gatewayIP();
+ IPAddress dnsIP(uint8_t dns_no = 0);
+ IPAddress broadcastIP();
+ IPAddress networkID();
+ uint8_t subnetCIDR();
+ bool enableIpV6();
+ IPv6Address localIPv6();
+ const char * ifkey(void);
+ const char * desc(void);
+ String impl_name(void);
+
+ // Event based getters
+ bool connected();
+ bool hasIP();
+
+ // ETH Handle APIs
+ uint8_t * macAddress(uint8_t* mac);
+ String macAddress();
+ bool fullDuplex();
+ bool linkUp();
+ uint8_t linkSpeed();
+ bool autoNegotiation();
+ uint32_t phyAddr();
+
+ // Info APIs
+ void printInfo(Print & out);
+
+ friend class WiFiClient;
+ friend class WiFiServer;
+
+#if ETH_SPI_SUPPORTS_CUSTOM
+ static esp_err_t _eth_spi_read(void *ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t data_len);
+ static esp_err_t _eth_spi_write(void *ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
+#endif
+
+ protected:
+#if ETH_SPI_SUPPORTS_CUSTOM
+ esp_err_t eth_spi_read(uint32_t cmd, uint32_t addr, void *data, uint32_t data_len);
+ esp_err_t eth_spi_write(uint32_t cmd, uint32_t addr, const void *data, uint32_t data_len);
+#endif
+
+ static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
+
+ private:
+ bool _eth_started;
+ esp_eth_handle_t _eth_handle;
+ esp_netif_t *_esp_netif;
+ uint8_t _eth_index;
+ eth_phy_type_t _phy_type;
+#if ETH_SPI_SUPPORTS_CUSTOM
+ SPIClass * _spi;
+#endif
+ uint8_t _spi_freq_mhz;
+ int8_t _pin_cs;
+ int8_t _pin_irq;
+ int8_t _pin_rst;
+ int8_t _pin_sck;
+ int8_t _pin_miso;
+ int8_t _pin_mosi;
+#if CONFIG_ETH_USE_ESP32_EMAC
+ int8_t _pin_mcd;
+ int8_t _pin_mdio;
+ int8_t _pin_power;
+ int8_t _pin_rmii_clock;
+#endif /* CONFIG_ETH_USE_ESP32_EMAC */
+
+ static bool ethDetachBus(void * bus_pointer);
+ bool beginSPI(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq, int rst,
+#if ETH_SPI_SUPPORTS_CUSTOM
+ SPIClass * spi,
+#endif
+ int sck, int miso, int mosi, spi_host_device_t spi_host, uint8_t spi_freq_mhz);
+};
+
+extern ETHClass2 ETH2;
+
+#endif /* _ETH_H_ */
diff --git a/src/MeshCore.h b/src/MeshCore.h
index 70cd0f0672..28e92b8b2a 100644
--- a/src/MeshCore.h
+++ b/src/MeshCore.h
@@ -65,6 +65,7 @@ class MainBoard {
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
virtual uint8_t getShutdownReason() const { return 0; }
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }
+ virtual void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet) { /* no op */ }
};
/**
@@ -100,4 +101,5 @@ class RTCClock {
}
};
-}
\ No newline at end of file
+}
+
diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp
index f3cba4062a..514d3834fe 100644
--- a/src/helpers/CommonCLI.cpp
+++ b/src/helpers/CommonCLI.cpp
@@ -22,6 +22,23 @@ static bool isValidName(const char *n) {
return true;
}
+// Helper functions for IP address conversion
+static uint32_t ipStringToUint32(const char* ip_str) {
+ uint32_t ip = 0;
+ uint8_t parts[4] = {0, 0, 0, 0};
+ sscanf(ip_str, "%hhu.%hhu.%hhu.%hhu", &parts[0], &parts[1], &parts[2], &parts[3]);
+ ip = ((uint32_t)parts[0] << 24) | ((uint32_t)parts[1] << 16) | ((uint32_t)parts[2] << 8) | parts[3];
+ return ip;
+}
+
+static void uint32ToIPString(uint32_t ip, char* buffer, size_t size) {
+ uint8_t b1 = (ip >> 24) & 0xFF;
+ uint8_t b2 = (ip >> 16) & 0xFF;
+ uint8_t b3 = (ip >> 8) & 0xFF;
+ uint8_t b4 = ip & 0xFF;
+ snprintf(buffer, size, "%d.%d.%d.%d", b1, b2, b3, b4);
+}
+
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
if (fs->exists("/com_prefs")) {
loadPrefsInt(fs, "/com_prefs"); // new filename
@@ -84,7 +101,13 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
// 290
-
+ file.read((uint8_t *)&_prefs->eth_ip, sizeof(_prefs->eth_ip)); // 290
+ file.read((uint8_t *)&_prefs->eth_gateway, sizeof(_prefs->eth_gateway)); // 294
+ file.read((uint8_t *)&_prefs->eth_subnet, sizeof(_prefs->eth_subnet)); // 298
+ file.read((uint8_t *)&_prefs->eth_dns1, sizeof(_prefs->eth_dns1)); // 302
+ file.read((uint8_t *)&_prefs->eth_dns2, sizeof(_prefs->eth_dns2)); // 306
+ // 310
+
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
_prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f);
@@ -111,6 +134,14 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
+ // sanitise bad ethernet pref values
+ // If values are 0 (not set), they will be initialized from platformio.ini defaults at boot
+ if (_prefs->eth_ip == 0) _prefs->eth_ip = 0; // 0 = use platformio.ini default
+ if (_prefs->eth_gateway == 0) _prefs->eth_gateway = 0;
+ if (_prefs->eth_subnet == 0) _prefs->eth_subnet = 0;
+ if (_prefs->eth_dns1 == 0) _prefs->eth_dns1 = 0;
+ if (_prefs->eth_dns2 == 0) _prefs->eth_dns2 = 0;
+
file.close();
}
}
@@ -171,7 +202,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
// 290
-
+ file.write((uint8_t *)&_prefs->eth_ip, sizeof(_prefs->eth_ip)); // 290
+ file.write((uint8_t *)&_prefs->eth_gateway, sizeof(_prefs->eth_gateway)); // 294
+ file.write((uint8_t *)&_prefs->eth_subnet, sizeof(_prefs->eth_subnet)); // 298
+ file.write((uint8_t *)&_prefs->eth_dns1, sizeof(_prefs->eth_dns1)); // 302
+ file.write((uint8_t *)&_prefs->eth_dns2, sizeof(_prefs->eth_dns2)); // 306
+ // 310
file.close();
}
}
@@ -382,6 +418,24 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
} else if (memcmp(config, "bridge.secret", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_secret);
+#endif
+#ifdef USE_ETHERNET
+ } else if (memcmp(config, "ip", 2) == 0) {
+ char ip_str[16];
+ uint32ToIPString(_prefs->eth_ip, ip_str, sizeof(ip_str));
+ sprintf(reply, "> %s", ip_str);
+ } else if (memcmp(config, "subnet", 6) == 0) {
+ char subnet_str[16];
+ uint32ToIPString(_prefs->eth_subnet, subnet_str, sizeof(subnet_str));
+ sprintf(reply, "> %s", subnet_str);
+ } else if (memcmp(config, "gw", 2) == 0) {
+ char gw_str[16];
+ uint32ToIPString(_prefs->eth_gateway, gw_str, sizeof(gw_str));
+ sprintf(reply, "> %s", gw_str);
+ } else if (memcmp(config, "dns", 3) == 0) {
+ char dns_str[16];
+ uint32ToIPString(_prefs->eth_dns1, dns_str, sizeof(dns_str));
+ sprintf(reply, "> %s", dns_str);
#endif
} else if (memcmp(config, "bootloader.ver", 14) == 0) {
#ifdef NRF52_PLATFORM
@@ -678,6 +732,44 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
};
+#ifdef USE_ETHERNET
+ } else if (memcmp(config, "ip ", 3) == 0) {
+ uint32_t ip = ipStringToUint32(&config[3]);
+ if (ip != 0) {
+ _prefs->eth_ip = ip;
+ savePrefs();
+ strcpy(reply, "OK - reboot to apply");
+ } else {
+ strcpy(reply, "Error: invalid IP format");
+ }
+ } else if (memcmp(config, "subnet ", 7) == 0) {
+ uint32_t subnet = ipStringToUint32(&config[7]);
+ if (subnet != 0) {
+ _prefs->eth_subnet = subnet;
+ savePrefs();
+ strcpy(reply, "OK - reboot to apply");
+ } else {
+ strcpy(reply, "Error: invalid subnet format");
+ }
+ } else if (memcmp(config, "gw ", 3) == 0) {
+ uint32_t gw = ipStringToUint32(&config[3]);
+ if (gw != 0) {
+ _prefs->eth_gateway = gw;
+ savePrefs();
+ strcpy(reply, "OK - reboot to apply");
+ } else {
+ strcpy(reply, "Error: invalid IP format");
+ }
+ } else if (memcmp(config, "dns ", 4) == 0) {
+ uint32_t dns = ipStringToUint32(&config[4]);
+ if (dns != 0) {
+ _prefs->eth_dns1 = dns;
+ savePrefs();
+ strcpy(reply, "OK - reboot to apply");
+ } else {
+ strcpy(reply, "Error: invalid IP format");
+ }
+#endif
} else {
sprintf(reply, "unknown config: %s", config);
}
diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h
index 51013640e6..87bc43014d 100644
--- a/src/helpers/CommonCLI.h
+++ b/src/helpers/CommonCLI.h
@@ -59,6 +59,12 @@ struct NodePrefs { // persisted to file
char owner_info[120];
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
+ // Ethernet settings
+ uint32_t eth_ip;
+ uint32_t eth_gateway;
+ uint32_t eth_subnet;
+ uint32_t eth_dns1;
+ uint32_t eth_dns2;
};
class CommonCLICallbacks {
diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp
index e0ca1d0eeb..de09e0f922 100644
--- a/src/helpers/ESP32Board.cpp
+++ b/src/helpers/ESP32Board.cpp
@@ -3,18 +3,29 @@
#include "ESP32Board.h"
#if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
-#include
#include
#include
#include
-
#include
+#if defined(USE_ETHERNET)
+ extern String eth_local_ip; // defined in TEthEliteBoard.cpp
+#else
+ #include
+#endif
+
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
- inhibit_sleep = true; // prevent sleep during OTA
- WiFi.softAP("MeshCore-OTA", NULL);
+ inhibit_sleep = true;
+#if defined(USE_ETHERNET)
+ Serial.println("OTA: using ETH");
+ sprintf(reply, "Started: http://%s/update", eth_local_ip.c_str());
+#else
+ Serial.println("OTA: using WiFi SoftAP");
+ WiFi.softAP("MeshCore-OTA", NULL);
sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str());
+#endif
+
MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply);
static char id_buf[60];
@@ -32,7 +43,7 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
});
AsyncElegantOTA.setID(id_buf);
- AsyncElegantOTA.begin(server); // Start ElegantOTA
+ AsyncElegantOTA.begin(server);
server->begin();
return true;
@@ -44,4 +55,4 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
}
#endif
-#endif
+#endif // ESP_PLATFORM
diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp
index 462e3ecc30..eb62457ac1 100644
--- a/src/helpers/esp32/SerialWifiInterface.cpp
+++ b/src/helpers/esp32/SerialWifiInterface.cpp
@@ -2,10 +2,9 @@
#include
void SerialWifiInterface::begin(int port) {
- // wifi setup is handled outside of this class, only starts the server
- server.begin(port);
+ server = WiFiServer(port);
+ server.begin();
}
-
// ---------- public methods
void SerialWifiInterface::enable() {
if (_isEnabled) return;
@@ -169,4 +168,5 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
bool SerialWifiInterface::isConnected() const {
return deviceConnected; //pServer != NULL && pServer->getConnectedCount() > 0;
-}
\ No newline at end of file
+}
+
diff --git a/src/helpers/esp32/TCPConsole.h b/src/helpers/esp32/TCPConsole.h
new file mode 100644
index 0000000000..9e4bd797ed
--- /dev/null
+++ b/src/helpers/esp32/TCPConsole.h
@@ -0,0 +1,144 @@
+#pragma once
+
+#if defined(ESP32) && defined(TCP_CONSOLE_PORT) && defined(ADMIN_PASSWORD)
+
+#include
+#include
+#include
+#include
+#include // for NodePrefs
+
+#ifndef TCP_CONSOLE_MAX_CLIENTS
+ #define TCP_CONSOLE_MAX_CLIENTS 2
+#endif
+
+#ifndef TCP_CONSOLE_TIMEOUT_MS
+ #define TCP_CONSOLE_TIMEOUT_MS 300000 // 5 minutes inactivity timeout
+#endif
+
+class TCPConsole {
+ WiFiServer _server;
+ WiFiClient _clients[TCP_CONSOLE_MAX_CLIENTS];
+ bool _authenticated[TCP_CONSOLE_MAX_CLIENTS];
+ unsigned long _last_active[TCP_CONSOLE_MAX_CLIENTS];
+ char _cmd_buf[TCP_CONSOLE_MAX_CLIENTS][160];
+ int _cmd_len[TCP_CONSOLE_MAX_CLIENTS];
+ NodePrefs* _prefs; // pointer to live NodePrefs — password is read at runtime
+
+ void sendToClient(int i, const char* msg) {
+ if (_clients[i] && _clients[i].connected()) {
+ _clients[i].print(msg);
+ }
+ }
+
+ void disconnectClient(int i) {
+ _clients[i].stop();
+ _authenticated[i] = false;
+ _cmd_buf[i][0] = 0;
+ _cmd_len[i] = 0;
+ }
+
+public:
+ TCPConsole(NodePrefs* prefs)
+ : _server(TCP_CONSOLE_PORT), _prefs(prefs) {
+ for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) {
+ _authenticated[i] = false;
+ _cmd_buf[i][0] = 0;
+ _cmd_len[i] = 0;
+ _last_active[i] = 0;
+ }
+ }
+
+ void begin() {
+ _server.begin();
+ Serial.printf("TCP Console listening on port %d\n", TCP_CONSOLE_PORT);
+ }
+
+ void setPrefs(NodePrefs* prefs) { _prefs = prefs; }
+
+ // Call this from loop(), passing the mesh's handleCommand function
+ template
+ void loop(T& mesh) {
+ // Accept new clients
+ WiFiClient newClient = _server.available();
+ if (newClient) {
+ for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) {
+ if (!_clients[i] || !_clients[i].connected()) {
+ _clients[i] = newClient;
+ _authenticated[i] = false;
+ _cmd_buf[i][0] = 0;
+ _cmd_len[i] = 0;
+ _last_active[i] = millis();
+ sendToClient(i, "MeshCore Console\r\nPassword: ");
+ break;
+ }
+ }
+ }
+
+ // Handle connected clients
+ for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) {
+ if (!_clients[i] || !_clients[i].connected()) continue;
+
+ // Inactivity timeout
+ if (millis() - _last_active[i] > TCP_CONSOLE_TIMEOUT_MS) {
+ sendToClient(i, "\r\nTimeout. Disconnecting.\r\n");
+ disconnectClient(i);
+ continue;
+ }
+
+ // Read available data
+ while (_clients[i].available()) {
+ _last_active[i] = millis();
+ char c = _clients[i].read();
+
+ if (c == '\n') continue; // ignore LF, handle CR only
+
+ if (c != '\r' && _cmd_len[i] < 158) {
+ _cmd_buf[i][_cmd_len[i]++] = c;
+ _cmd_buf[i][_cmd_len[i]] = 0;
+ if (!_authenticated[i]) {
+ sendToClient(i, "*"); // mask password input
+ } else {
+ _clients[i].print(c); // echo command
+ }
+ continue;
+ }
+
+ // Got CR — process command
+ sendToClient(i, "\r\n");
+ _cmd_buf[i][_cmd_len[i]] = 0;
+
+ if (!_authenticated[i]) {
+ // Authentication — always read from live NodePrefs, not compile-time constant
+ if (_prefs != nullptr && strcmp(_cmd_buf[i], _prefs->password) == 0) {
+ _authenticated[i] = true;
+ char welcome[80];
+ snprintf(welcome, sizeof(welcome), "Welcome to %s console.\r\n> ", _prefs->node_name);
+ sendToClient(i, welcome);
+ } else {
+ sendToClient(i, "Wrong password. Disconnecting.\r\n");
+ disconnectClient(i);
+ }
+ } else {
+ // Execute command
+ if (strlen(_cmd_buf[i]) > 0) {
+ char reply[160];
+ reply[0] = 0;
+ mesh.handleCommand(0, _cmd_buf[i], reply);
+ if (reply[0]) {
+ sendToClient(i, " -> ");
+ sendToClient(i, reply);
+ sendToClient(i, "\r\n");
+ }
+ }
+ sendToClient(i, "> ");
+ }
+
+ _cmd_buf[i][0] = 0;
+ _cmd_len[i] = 0;
+ }
+ }
+ }
+};
+
+#endif // ESP32 && TCP_CONSOLE_PORT && ADMIN_PASSWORD
diff --git a/src/helpers/esp32/TEthEliteBoard.cpp b/src/helpers/esp32/TEthEliteBoard.cpp
new file mode 100644
index 0000000000..ac3eafc4fa
--- /dev/null
+++ b/src/helpers/esp32/TEthEliteBoard.cpp
@@ -0,0 +1,193 @@
+#if defined(T_ETH_ELITE_SX1262) || defined(T_ETH_ELITE_SX1276)
+
+#include
+#include
+#include "driver/spi_common.h"
+#include "driver/gpio.h"
+#include
+#include "TEthEliteBoard.h"
+#include "target.h"
+#include "helpers/ui/MomentaryButton.h"
+
+extern MomentaryButton user_btn;
+
+uint32_t deviceOnline = 0x00;
+static SPIClass spi_eth(FSPI);
+static ETHClass2 ETH;
+String eth_local_ip; // global, accessible from ESP32Board.cpp via extern
+
+void TEthEliteBoard::begin() {
+ ESP32Board::begin();
+ user_btn.begin();
+ Wire1.begin(PIN_BOARD_SDA1, PIN_BOARD_SCL1);
+ #if ENV_INCLUDE_GPS
+ Serial1.begin(9600, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
+ #endif
+
+#ifdef USE_ETHERNET
+ WiFi.mode(WIFI_OFF);
+#endif
+ startNetwork();
+
+ pinMode(P_LORA_TX_LED, OUTPUT);
+ digitalWrite(P_LORA_TX_LED, LOW);
+
+ esp_reset_reason_t reason = esp_reset_reason();
+ if (reason == ESP_RST_DEEPSLEEP) {
+#if defined(T_ETH_ELITE_SX1262)
+ long wakeup_source = esp_sleep_get_ext1_wakeup_status();
+ if (wakeup_source & (1 << P_LORA_DIO_1)) {
+ 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);
+#elif defined(T_ETH_ELITE_SX1276)
+ long wakeup_source = esp_sleep_get_ext1_wakeup_status();
+ if (wakeup_source & (1 << P_LORA_DIO_0)) {
+ 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_0);
+#endif
+ }
+}
+
+#ifdef MESH_DEBUG
+void TEthEliteBoard::scanDevices(TwoWire *w)
+{
+ uint8_t err, addr;
+ int nDevices = 0;
+
+ Serial.println("Scanning I2C for Devices");
+ for (addr = 1; addr < 127; addr++) {
+ 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.printf("\tI2C device found at address 0x%02X\n", addr);
+ break;
+ }
+ } else if (err == 4) {
+ Serial.printf("Unknown error at address 0x%02X\n", addr);
+ }
+ }
+ if (nDevices == 0)
+ Serial.println("No I2C devices found");
+
+ Serial.println("Scan complete.");
+ Serial.printf("GPS RX pin: %d, GPS TX pin: %d\n", PIN_GPS_RX, PIN_GPS_TX);
+}
+
+void TEthEliteBoard::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");
+ if (PMU->isBatteryConnect()) {
+ Serial.print("getBatteryPercent:"); Serial.print(PMU->getBatteryPercent()); Serial.println("%");
+ }
+}
+#endif
+
+void TEthEliteBoard::startNetwork() {
+#ifdef USE_ETHERNET
+ startEthernet();
+#elif defined(WIFI_SSID)
+ startWifi();
+#endif
+}
+
+void TEthEliteBoard::startEthernet() {
+ pinMode(ETH_CS, OUTPUT);
+ digitalWrite(ETH_CS, HIGH);
+
+ ETH.begin(ETH_PHY_W5500, ETH_ADDR, ETH_CS, ETH_INT, -1, SPI2_HOST, ETH_SCLK, ETH_MISO, ETH_MOSI);
+ delay(100);
+
+#ifndef USE_ETHERNET
+ // Used only if USE_ETHERNET is not enabled
+ #ifdef ETH_STATIC_IP
+ IPAddress ip(ETH_STATIC_IP);
+ IPAddress gw(ETH_GATEWAY);
+ IPAddress mask(ETH_SUBNET);
+ IPAddress dns(ETH_DNS);
+ ETH.config(ip, gw, mask, dns);
+ #endif
+#endif
+
+ unsigned long t0 = millis();
+ while (!ETH.linkUp() && millis() - t0 < 5000) {
+ delay(100);
+ }
+
+ t0 = millis();
+ while (ETH.localIP() == IPAddress(0, 0, 0, 0) && millis() - t0 < 5000) {
+ delay(100);
+ }
+
+ if (ETH.localIP() == IPAddress(0, 0, 0, 0)) {
+ Serial.println("DHCP timeout, using fallback IP");
+ ETH.config(IPAddress(192, 168, 4, 2), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
+ }
+
+ eth_local_ip = ETH.localIP().toString(); // save IP for OTA use
+
+ uint8_t mac[6];
+ ETH.macAddress(mac);
+ Serial.printf("ETH MAC %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ Serial.print("ETH IP "); Serial.println(ETH.localIP());
+ Serial.println(ETH.linkUp() ? "ETH LINK UP" : "ETH LINK DOWN");
+}
+void TEthEliteBoard::reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet) {
+ if (ip != 0) {
+ uint8_t b1 = (ip >> 24) & 0xFF;
+ uint8_t b2 = (ip >> 16) & 0xFF;
+ uint8_t b3 = (ip >> 8) & 0xFF;
+ uint8_t b4 = ip & 0xFF;
+
+ uint8_t gw1 = (gw >> 24) & 0xFF;
+ uint8_t gw2 = (gw >> 16) & 0xFF;
+ uint8_t gw3 = (gw >> 8) & 0xFF;
+ uint8_t gw4 = gw & 0xFF;
+
+ uint8_t sub1 = (subnet >> 24) & 0xFF;
+ uint8_t sub2 = (subnet >> 16) & 0xFF;
+ uint8_t sub3 = (subnet >> 8) & 0xFF;
+ uint8_t sub4 = subnet & 0xFF;
+
+ ETH.config(
+ IPAddress(b1, b2, b3, b4),
+ IPAddress(gw1, gw2, gw3, gw4),
+ IPAddress(sub1, sub2, sub3, sub4)
+ );
+ Serial.printf("ETH reconfigured to %d.%d.%d.%d\n", b1, b2, b3, b4);
+ eth_local_ip = ETH.localIP().toString(); // Aggiorna IP locale per OTA
+ }
+}
+#endif
diff --git a/src/helpers/esp32/TEthEliteBoard.h b/src/helpers/esp32/TEthEliteBoard.h
new file mode 100644
index 0000000000..d009be3f43
--- /dev/null
+++ b/src/helpers/esp32/TEthEliteBoard.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#if defined(T_ETH_ELITE_SX1262)
+ #include "TEthEliteBoard_SX1262.h"
+#elif defined(T_ETH_ELITE_SX1276)
+ #include "TEthEliteBoard_SX1276.h"
+#endif
diff --git a/src/helpers/esp32/TEthEliteBoard_SX1262.h b/src/helpers/esp32/TEthEliteBoard_SX1262.h
new file mode 100644
index 0000000000..e212e0e310
--- /dev/null
+++ b/src/helpers/esp32/TEthEliteBoard_SX1262.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#if defined(T_ETH_ELITE_SX1262)
+
+// Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_1
+
+// LoRa SX1262 pins (T-ETH Elite LoRa Shield)
+#define P_LORA_NSS 40 // CS
+#define P_LORA_RESET 46 // RESET
+#define P_LORA_BUSY 16 // BUSY
+#define P_LORA_DIO_0 -1 // NC
+#define P_LORA_DIO_1 8 // IRQ
+#define P_LORA_TX_LED 38
+#define P_LORA_SCLK 10
+#define P_LORA_MISO 9
+#define P_LORA_MOSI 11
+
+// ETH W5500 pins (T-ETH Elite main board)
+#define ETH_MISO 47
+#define ETH_MOSI 21
+#define ETH_SCLK 48
+#define ETH_CS 45
+#define ETH_INT 14
+#define ETH_RST -1
+#define ETH_ADDR 1
+
+// I2C bus (Wire1)
+#define PIN_BOARD_SDA1 17
+#define PIN_BOARD_SCL1 18
+
+// GPS pins (LoRa Shield)
+#define PIN_GPS_RX 39
+#define PIN_GPS_TX 42
+#define PIN_GPS_EN -1
+
+// Analog button
+// #define PIN_USER_BTN_ANA 7
+
+// Include headers AFTER pin definitions so ESP32Board::sleep() can use P_LORA_DIO_1
+#include
+#include
+#include "XPowersLib.h"
+#include "helpers/ESP32Board.h"
+#include
+
+class TEthEliteBoard : public ESP32Board {
+ XPowersLibInterface *PMU = NULL;
+
+ 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),
+ };
+
+public:
+
+#ifdef MESH_DEBUG
+ void printPMU();
+ void scanDevices(TwoWire *w);
+#endif
+ void begin();
+ void startNetwork();
+ void startEthernet();
+ void startWifi();
+ void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet);
+
+ void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
+ esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+
+ 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);
+ } else {
+ esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
+ }
+
+ if (secs > 0) {
+ esp_sleep_enable_timer_wakeup(secs * 1000000);
+ }
+
+ esp_deep_sleep_start();
+ }
+
+ uint16_t getBattMilliVolts() { return 0; }
+
+ const char* getManufacturerName() const { return "LilyGo T-ETH-Elite"; }
+};
+
+#endif
diff --git a/src/helpers/esp32/TEthEliteBoard_SX1276.h b/src/helpers/esp32/TEthEliteBoard_SX1276.h
new file mode 100644
index 0000000000..54e0742d9b
--- /dev/null
+++ b/src/helpers/esp32/TEthEliteBoard_SX1276.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#if defined(T_ETH_ELITE_SX1276)
+
+// Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_0
+
+// LoRa SX1276 pins (T-ETH Elite LoRa Shield)
+#define P_LORA_DIO_0 8 // IRQ (DIO0 on SX1276)
+#define P_LORA_DIO_1 16 // DIO1
+#define P_LORA_NSS 40 // CS
+#define P_LORA_RESET 46 // RESET
+#define P_LORA_BUSY -1 // SX1276 has no BUSY pin
+#define P_LORA_TX_LED 38
+#define P_LORA_SCLK 10
+#define P_LORA_MISO 9
+#define P_LORA_MOSI 11
+
+// ETH W5500 pins (T-ETH Elite main board)
+#define ETH_MISO 47
+#define ETH_MOSI 21
+#define ETH_SCLK 48
+#define ETH_CS 45
+#define ETH_INT 14
+#define ETH_RST -1
+#define ETH_ADDR 1
+
+// I2C bus (Wire1)
+#define PIN_BOARD_SDA1 17
+#define PIN_BOARD_SCL1 18
+
+// GPS pins (LoRa Shield)
+#define PIN_GPS_RX 39
+#define PIN_GPS_TX 42
+#define PIN_GPS_EN -1
+
+// Analog button
+// #define PIN_USER_BTN_ANA 7
+
+// Include headers AFTER pin definitions so ESP32Board::sleep() can use P_LORA_DIO_0
+#include
+#include
+#include "XPowersLib.h"
+#include "helpers/ESP32Board.h"
+#include
+
+class TEthEliteBoard : public ESP32Board {
+ XPowersLibInterface *PMU = NULL;
+
+ 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),
+ };
+
+public:
+
+#ifdef MESH_DEBUG
+ void printPMU();
+ void scanDevices(TwoWire *w);
+#endif
+ void begin();
+ void startNetwork();
+ void startEthernet();
+ void startWifi();
+ void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet);
+
+ void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
+ esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+
+ rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
+ rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
+ rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
+
+ if (pin_wake_btn < 0) {
+ esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH);
+ } else {
+ esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
+ }
+
+ if (secs > 0) {
+ esp_sleep_enable_timer_wakeup(secs * 1000000);
+ }
+
+ esp_deep_sleep_start();
+ }
+
+ uint16_t getBattMilliVolts() { return 0; }
+
+ const char* getManufacturerName() const { return "LilyGo T-ETH-Elite"; }
+};
+
+#endif
diff --git a/variants/lilygo_t_eth_elite_sx1262/platformio.ini b/variants/lilygo_t_eth_elite_sx1262/platformio.ini
new file mode 100644
index 0000000000..e0923f1947
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1262/platformio.ini
@@ -0,0 +1,207 @@
+[LilyGo_T_ETH_Elite_SX1262]
+extends = esp32_base
+board = t_eth_elite
+upload_protocol = esptool
+board_build.partitions = default_16MB.csv
+board_upload.flash_size = 16MB
+board_upload.maximum_size = 16777216
+build_flags =
+ ${esp32_base.build_flags}
+ -I variants/lilygo_t_eth_elite_sx1262
+ -D T_ETH_ELITE_SX1262
+ -D RADIO_CLASS=CustomSX1262
+ -D WRAPPER_CLASS=CustomSX1262Wrapper
+ -D SX126X_CURRENT_LIMIT=140
+ -D SX126X_RX_BOOSTED_GAIN=1
+ -D LORA_TX_POWER=22
+ -D ENV_INCLUDE_GPS=1
+ -D PIN_GPS_RX=39
+ -D PIN_GPS_TX=42
+ -D PIN_GPS_EN=-1
+ -D PIN_USER_BTN_ANA=7
+
+build_src_filter = ${esp32_base.build_src_filter}
+ +<../variants/lilygo_t_eth_elite_sx1262>
+ +
+ +
+lib_deps =
+ ${esp32_base.lib_deps}
+ lewisxhe/XPowersLib @ ^0.2.7
+ stevemarple/MicroNMEA @ ^2.0.6
+ adafruit/Adafruit BME280 Library @ ^2.3.0
+
+; === LilyGo T-ETH-Elite with SX1262 environments ===
+
+[env:LilyGo_T_ETH_Elite_SX1262_repeater]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 Repeater"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_repeater_eth]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 Repeater eth"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,21
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_repeater_bridge_espnow]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 ESPNow Bridge"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D WITH_ESPNOW_BRIDGE=1
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_repeater_bridge_espnow_eth]
+extends = LilyGo_T_ETH_Elite_SX1262
+upload_speed = 115200
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 ESPNow Bridge eth"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D WITH_ESPNOW_BRIDGE=1
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,21
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_room_server]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 Room"'
+ -D ADVERT_LAT=0.0
+ -D ADVERT_LON=0.0
+ -D ADMIN_PASSWORD='"password"'
+ -D ROOM_PASSWORD='"hello"'
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +<../examples/simple_room_server>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_room_server_eth]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1262 Room eth"'
+ -D ADVERT_LAT=0.0
+ -D ADVERT_LON=0.0
+ -D ADMIN_PASSWORD='"password"'
+ -D ROOM_PASSWORD='"hello"'
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,20
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +<../examples/simple_room_server>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1262_companion_radio_eth]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D OFFLINE_QUEUE_SIZE=256
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,20
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ densaugeo/base64 @ ~1.4.0
+
+[env:LilyGo_T_ETH_Elite_SX1262_companion_radio_wifi]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D OFFLINE_QUEUE_SIZE=256
+ -D WIFI_SSID='"WIFI_SSID"'
+ -D WIFI_PWD='"Password"'
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ densaugeo/base64 @ ~1.4.0
+
+[env:LilyGo_T_ETH_Elite_SX1262_companion_radio_ble]
+extends = LilyGo_T_ETH_Elite_SX1262
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1262.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D BLE_PIN_CODE=123456
+ -D OFFLINE_QUEUE_SIZE=256
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1262.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1262.lib_deps}
+ densaugeo/base64 @ ~1.4.0
diff --git a/variants/lilygo_t_eth_elite_sx1262/target.cpp b/variants/lilygo_t_eth_elite_sx1262/target.cpp
new file mode 100644
index 0000000000..c42236f3c0
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1262/target.cpp
@@ -0,0 +1,62 @@
+#include
+#include "helpers/ui/MomentaryButton.h"
+#include "target.h"
+
+TEthEliteBoard board;
+
+#ifdef DISPLAY_CLASS
+ DISPLAY_CLASS display;
+#endif
+
+#ifdef PIN_USER_BTN_ANA
+// If the analog pin is defined, use it for both button instances
+MomentaryButton user_btn(PIN_USER_BTN_ANA, 1000, true);
+MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, true); // alias for UITask analog button support
+#else
+// If NOT defined, route user_btn to pin 0
+// and create analog_btn as a dummy alias to satisfy UITask requirements
+MomentaryButton user_btn(0, 1000, true);
+MomentaryButton analog_btn(0, 1000, true);
+#endif
+
+static SPIClass spi;
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+#if ENV_INCLUDE_GPS
+ #include
+ MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
+ EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
+#else
+ EnvironmentSensorManager sensors;
+#endif
+
+bool radio_init() {
+ fallback_clock.begin();
+ rtc_clock.begin(Wire1);
+ return radio.std_init(&spi);
+}
+
+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);
+}
diff --git a/variants/lilygo_t_eth_elite_sx1262/target.h b/variants/lilygo_t_eth_elite_sx1262/target.h
new file mode 100644
index 0000000000..674b95af58
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1262/target.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern MomentaryButton user_btn;
+
+#ifdef PIN_USER_BTN_ANA
+ extern MomentaryButton analog_btn;
+#endif
+
+#ifdef DISPLAY_CLASS
+ #include
+ extern DISPLAY_CLASS display;
+#endif
+
+extern TEthEliteBoard board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
+
+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();
diff --git a/variants/lilygo_t_eth_elite_sx1276/platformio.ini b/variants/lilygo_t_eth_elite_sx1276/platformio.ini
new file mode 100644
index 0000000000..4a677c7b65
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1276/platformio.ini
@@ -0,0 +1,204 @@
+[LilyGo_T_ETH_Elite_SX1276]
+extends = esp32_base
+board = t_eth_elite
+upload_protocol = esptool
+board_build.partitions = default_16MB.csv
+board_upload.flash_size = 16MB
+board_upload.maximum_size = 16777216
+build_flags =
+ ${esp32_base.build_flags}
+ -I variants/lilygo_t_eth_elite_sx1276
+ -D T_ETH_ELITE_SX1276
+ -D RADIO_CLASS=CustomSX1276
+ -D WRAPPER_CLASS=CustomSX1276Wrapper
+ -D SX127X_CURRENT_LIMIT=120
+ -D LORA_TX_POWER=20
+ -D ENV_INCLUDE_GPS=1
+ -D PIN_GPS_RX=39
+ -D PIN_GPS_TX=42
+ -D PIN_GPS_EN=-1
+ -D PIN_USER_BTN_ANA=7
+build_src_filter = ${esp32_base.build_src_filter}
+ +<../variants/lilygo_t_eth_elite_sx1276>
+ +
+ +
+lib_deps =
+ ${esp32_base.lib_deps}
+ lewisxhe/XPowersLib @ ^0.2.7
+ stevemarple/MicroNMEA @ ^2.0.6
+ adafruit/Adafruit BME280 Library @ ^2.3.0
+
+; === LilyGo T-ETH-Elite with SX1276 environments ===
+
+[env:LilyGo_T_ETH_Elite_SX1276_repeater]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 Repeater"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_repeater_eth]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 Repeater eth"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,21
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_repeater_bridge_espnow]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 ESPNow Bridge"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D WITH_ESPNOW_BRIDGE=1
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_repeater_bridge_espnow_eth]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 ESPNow Bridge eth"'
+ -D ADVERT_LAT=0
+ -D ADVERT_LON=0
+ -D ADMIN_PASSWORD='"password"'
+ -D MAX_NEIGHBOURS=50
+ -D WITH_ESPNOW_BRIDGE=1
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,20
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +
+ +<../examples/simple_repeater>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_room_server]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 Room"'
+ -D ADVERT_LAT=0.0
+ -D ADVERT_LON=0.0
+ -D ADMIN_PASSWORD='"password"'
+ -D ROOM_PASSWORD='"hello"'
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +<../examples/simple_room_server>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_room_server_eth]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -D ADVERT_NAME='"T-ETH Elite SX1276 Room eth"'
+ -D ADVERT_LAT=0.0
+ -D ADVERT_LON=0.0
+ -D ADMIN_PASSWORD='"password"'
+ -D ROOM_PASSWORD='"hello"'
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,20
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +<../examples/simple_room_server>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ ${esp32_ota.lib_deps}
+
+[env:LilyGo_T_ETH_Elite_SX1276_companion_radio_eth]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D OFFLINE_QUEUE_SIZE=256
+ -D USE_ETHERNET
+ -D ETH_STATIC_IP=192,168,254,20
+ -D ETH_GATEWAY=192,168,254,254
+ -D ETH_SUBNET=255,255,255,0
+ -D ETH_DNS=8,8,8,8
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ densaugeo/base64 @ ~1.4.0
+
+[env:LilyGo_T_ETH_Elite_SX1276_companion_radio_wifi]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D OFFLINE_QUEUE_SIZE=256
+ -D WIFI_SSID='"WIFI_SSID"'
+ -D WIFI_PWD='"Password"'
+ -D TCP_CONSOLE_PORT=4242
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ densaugeo/base64 @ ~1.4.0
+
+[env:LilyGo_T_ETH_Elite_SX1276_companion_radio_ble]
+extends = LilyGo_T_ETH_Elite_SX1276
+build_flags =
+ ${LilyGo_T_ETH_Elite_SX1276.build_flags}
+ -I examples/companion_radio/ui-new
+ -D MAX_CONTACTS=350
+ -D MAX_GROUP_CHANNELS=40
+ -D BLE_PIN_CODE=123456
+ -D OFFLINE_QUEUE_SIZE=256
+build_src_filter = ${LilyGo_T_ETH_Elite_SX1276.build_src_filter}
+ +
+ +
+ +<../examples/companion_radio/*.cpp>
+ +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+ ${LilyGo_T_ETH_Elite_SX1276.lib_deps}
+ densaugeo/base64 @ ~1.4.0
diff --git a/variants/lilygo_t_eth_elite_sx1276/target.cpp b/variants/lilygo_t_eth_elite_sx1276/target.cpp
new file mode 100644
index 0000000000..b0cb78bec2
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1276/target.cpp
@@ -0,0 +1,65 @@
+#include
+#include "helpers/ui/MomentaryButton.h"
+#include "target.h"
+
+TEthEliteBoard board;
+
+#ifdef DISPLAY_CLASS
+ DISPLAY_CLASS display;
+#endif
+
+#ifdef PIN_USER_BTN_ANA
+// If the analog pin is defined, use it for both button instances
+MomentaryButton user_btn(PIN_USER_BTN_ANA, 1000, true);
+MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, true); // alias for UITask analog button support
+#else
+// If NOT defined, route user_btn to pin 0
+// and create analog_btn as a dummy alias to satisfy UITask requirements
+MomentaryButton user_btn(0, 1000, true);
+MomentaryButton analog_btn(0, 1000, true);
+#endif
+
+static SPIClass spi;
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+#if ENV_INCLUDE_GPS
+ #include
+ MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
+ EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
+#else
+ EnvironmentSensorManager sensors;
+#endif
+
+bool radio_init() {
+ fallback_clock.begin();
+ rtc_clock.begin(Wire1);
+ spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
+ bool ok = radio.std_init(&spi);
+ delay(100); // allow SX1276 to stabilize after init
+ return ok;
+}
+
+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);
+}
diff --git a/variants/lilygo_t_eth_elite_sx1276/target.h b/variants/lilygo_t_eth_elite_sx1276/target.h
new file mode 100644
index 0000000000..3685c50619
--- /dev/null
+++ b/variants/lilygo_t_eth_elite_sx1276/target.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern MomentaryButton user_btn;
+
+#ifdef PIN_USER_BTN_ANA
+ extern MomentaryButton analog_btn;
+#endif
+
+#ifdef DISPLAY_CLASS
+ #include
+ extern DISPLAY_CLASS display;
+#endif
+
+extern TEthEliteBoard board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
+
+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();