Skip to content

Commit 3bbc51e

Browse files
committed
feat(backend): add ethernet support
1 parent 37565da commit 3bbc51e

File tree

10 files changed

+417
-2
lines changed

10 files changed

+417
-2
lines changed

docs/buildprocess.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Customize the settings as you see fit. A value of 0 will disable the specified f
4040
-D FT_DOWNLOAD_FIRMWARE=1
4141
-D FT_SLEEP=1
4242
-D FT_BATTERY=1
43+
-D FT_ETHERNET=1
4344
```
4445

4546
| Flag | Description |
@@ -51,6 +52,7 @@ Customize the settings as you see fit. A value of 0 will disable the specified f
5152
| FT_DOWNLOAD_FIRMWARE | Controls whether the firmware download feature is enabled. Disable this if you won't firmware pulled from a server. |
5253
| FT_SLEEP | Controls whether the deep sleep feature is enabled. Disable this if your device is not battery operated or you don't need to place it in deep sleep to save energy. |
5354
| FT_BATTERY | Controls whether the battery state of charge shall be reported to the clients. Disable this if your device is not battery operated. |
55+
| FT_ETHERNET | Controls whether an ethernet interface will be used. Disable this if your device has no ethernet interface connected. |
5456

5557
In addition custom features might be added or removed at runtime. See [Custom Features](statefulservice.md#custom-features) on how to use this in your application.
5658

features.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ build_flags =
99
-D FT_BATTERY=0
1010
-D FT_ANALYTICS=1
1111
-D FT_COREDUMP=1
12+
; -D FT_ETHERNET=1 ; ethernet feature should be enabled in the board config as not every board supports ethernet

lib/framework/ESP32SvelteKit.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd
2323
_wifiStatus(server, &_securitySettingsService),
2424
_apSettingsService(server, &ESPFS, &_securitySettingsService),
2525
_apStatus(server, &_securitySettingsService, &_apSettingsService),
26+
#if FT_ENABLED(FT_ETHERNET)
27+
_ethernetSettingsService(server, &ESPFS, &_securitySettingsService, &_socket),
28+
_ethernetStatus(server, &_securitySettingsService),
29+
#endif
2630
_socket(server, &_securitySettingsService, AuthenticationPredicates::IS_AUTHENTICATED),
2731
_notificationService(&_socket),
2832
#if FT_ENABLED(FT_NTP)
@@ -65,6 +69,10 @@ void ESP32SvelteKit::begin()
6569
ESP_LOGV(SVK_TAG, "Loading settings from files system");
6670
ESPFS.begin(true);
6771

72+
#if FT_ENABLED(FT_ETHERNET)
73+
_ethernetSettingsService.initEthernet();
74+
#endif
75+
6876
_wifiSettingsService.initWiFi();
6977

7078
// SvelteKit uses a lot of handlers, so we need to increase the max_uri_handlers
@@ -150,6 +158,11 @@ void ESP32SvelteKit::begin()
150158
_wifiSettingsService.begin();
151159
_wifiScanner.begin();
152160
_wifiStatus.begin();
161+
#if FT_ENABLED(FT_ETHERNET)
162+
_ethernetSettingsService.begin();
163+
_ethernetStatus.begin();
164+
#endif
165+
153166

154167
#if FT_ENABLED(FT_COREDUMP)
155168
_coreDump.begin();
@@ -223,6 +236,10 @@ void ESP32SvelteKit::_loop()
223236
bool ap = false;
224237
bool event = false;
225238
bool mqtt = false;
239+
#if FT_ENABLED(FT_ETHERNET)
240+
bool eth = false;
241+
#endif
242+
bool wifi_eth_combined = false;
226243

227244
while (1)
228245
{
@@ -234,21 +251,27 @@ void ESP32SvelteKit::_loop()
234251
#if FT_ENABLED(FT_ANALYTICS)
235252
_analyticsService.loop();
236253
#endif
254+
#if FT_ENABLED(FT_ETHERNET)
255+
_ethernetSettingsService.loop();
256+
eth = _ethernetStatus.isConnected();
257+
if (eth) { wifi_eth_combined = true; }
258+
#endif
237259

238260
// Query the connectivity status
239261
wifi = _wifiStatus.isConnected();
262+
if (wifi) { wifi_eth_combined = true; }
240263
ap = _apStatus.isActive();
241264
event = _socket.getConnectedClients() > 0;
242265
#if FT_ENABLED(FT_MQTT)
243266
mqtt = _mqttStatus.isConnected();
244267
#endif
245268

246269
// Update the system status
247-
if (wifi && mqtt)
270+
if (wifi_eth_combined && mqtt)
248271
{
249272
_connectionStatus = ConnectionStatus::STA_MQTT;
250273
}
251-
else if (wifi)
274+
else if (wifi_eth_combined)
252275
{
253276
_connectionStatus = event ? ConnectionStatus::STA_CONNECTED : ConnectionStatus::STA;
254277
}

lib/framework/ESP32SvelteKit.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
#include <WiFiScanner.h>
4343
#include <WiFiSettingsService.h>
4444
#include <WiFiStatus.h>
45+
#include <EthernetSettingsService.h>
46+
#include <EthernetStatus.h>
4547
#include <ESPFS.h>
4648
#include <PsychicHttp.h>
4749
#include <vector>
@@ -212,6 +214,10 @@ class ESP32SvelteKit
212214
WiFiStatus _wifiStatus;
213215
APSettingsService _apSettingsService;
214216
APStatus _apStatus;
217+
#if FT_ENABLED(FT_ETHERNET)
218+
EthernetSettingsService _ethernetSettingsService;
219+
EthernetStatus _ethernetStatus;
220+
#endif
215221
EventSocket _socket;
216222
NotificationService _notificationService;
217223
#if FT_ENABLED(FT_NTP)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* ESP32 SvelteKit
3+
*
4+
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
5+
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
6+
* https://github.com/theelims/ESP32-sveltekit
7+
*
8+
* Copyright (C) 2018 - 2023 rjwats
9+
* Copyright (C) 2023 - 2025 theelims
10+
*
11+
* All Rights Reserved. This software may be modified and distributed under
12+
* the terms of the LGPL v3 license. See the LICENSE file for details.
13+
**/
14+
15+
#include <EthernetSettingsService.h>
16+
17+
EthernetSettingsService::EthernetSettingsService(PsychicHttpServer *server,
18+
FS *fs,
19+
SecurityManager *securityManager,
20+
EventSocket *socket) : _server(server),
21+
_securityManager(securityManager),
22+
_httpEndpoint(EthernetSettings::read, EthernetSettings::update, this, server, ETHERNET_SETTINGS_SERVICE_PATH, securityManager,
23+
AuthenticationPredicates::IS_ADMIN),
24+
_fsPersistence(EthernetSettings::read, EthernetSettings::update, this, fs, ETHERNET_SETTINGS_FILE),
25+
_socket(socket)
26+
{
27+
addUpdateHandler([&](const String &originId)
28+
{ reconfigureEthernet(); },
29+
false);
30+
}
31+
32+
void EthernetSettingsService::initEthernet()
33+
{
34+
// make sure the interface is stopped before continuing and initializing
35+
ETH.end();
36+
_fsPersistence.readFromFS();
37+
configureNetwork(_state.ethernetSettings);
38+
}
39+
40+
void EthernetSettingsService::begin()
41+
{
42+
_socket->registerEvent(EVENT_ETHERNET);
43+
_httpEndpoint.begin();
44+
}
45+
46+
void EthernetSettingsService::loop()
47+
{
48+
unsigned long currentMillis = millis();
49+
50+
if (!_lastEthernetUpdate || (unsigned long)(currentMillis - _lastEthernetUpdate) >= ETHERNET_EVENT_DELAY)
51+
{
52+
_lastEthernetUpdate = currentMillis;
53+
updateEthernet();
54+
}
55+
}
56+
57+
String EthernetSettingsService::getHostname()
58+
{
59+
return _state.hostname;
60+
}
61+
62+
String EthernetSettingsService::getIP()
63+
{
64+
if (ETH.connected())
65+
{
66+
return ETH.localIP().toString();
67+
}
68+
return "Not connected";
69+
}
70+
71+
void EthernetSettingsService::configureNetwork(ethernet_settings_t &network)
72+
{
73+
// set hostname before IP configuration starts
74+
ETH.setHostname(_state.hostname.c_str());
75+
if (network.staticIPConfig)
76+
{
77+
// configure for static IP
78+
ETH.config(network.localIP, network.gatewayIP, network.subnetMask, network.dnsIP1, network.dnsIP2);
79+
}
80+
else
81+
{
82+
// configure for DHCP
83+
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
84+
}
85+
// (re)start ethernet
86+
ETH.begin();
87+
// set hostname (again) after (re)starting ethernet due to a bug in the ESP-IDF implementation
88+
ETH.setHostname(_state.hostname.c_str());
89+
90+
}
91+
92+
void EthernetSettingsService::reconfigureEthernet()
93+
{
94+
configureNetwork(_state.ethernetSettings);
95+
}
96+
97+
void EthernetSettingsService::updateEthernet()
98+
{
99+
JsonDocument doc;
100+
doc["connected"] = ETH.connected();
101+
JsonObject jsonObject = doc.as<JsonObject>();
102+
_socket->emitEvent(EVENT_ETHERNET, jsonObject);
103+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#ifndef EthernetSettingsService_h
2+
#define EthernetSettingsService_h
3+
4+
/**
5+
* ESP32 SvelteKit
6+
*
7+
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
8+
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
9+
* https://github.com/theelims/ESP32-sveltekit
10+
*
11+
* Copyright (C) 2018 - 2023 rjwats
12+
* Copyright (C) 2023 - 2025 theelims
13+
*
14+
* All Rights Reserved. This software may be modified and distributed under
15+
* the terms of the LGPL v3 license. See the LICENSE file for details.
16+
**/
17+
18+
#include <WiFi.h>
19+
#include <ETH.h>
20+
#include <SettingValue.h>
21+
#include <StatefulService.h>
22+
#include <EventSocket.h>
23+
#include <FSPersistence.h>
24+
#include <HttpEndpoint.h>
25+
#include <JsonUtils.h>
26+
#include <SecurityManager.h>
27+
#include <PsychicHttp.h>
28+
#include <vector>
29+
30+
#ifndef FACTORY_ETHERNET_HOSTNAME
31+
#define FACTORY_ETHERNET_HOSTNAME "#{platform}-#{unique_id}"
32+
#endif
33+
34+
#define ETHERNET_EVENT_DELAY 500
35+
36+
#define ETHERNET_SETTINGS_FILE "/config/ethernetSettings.json"
37+
#define ETHERNET_SETTINGS_SERVICE_PATH "/rest/ethernetSettings"
38+
39+
#define EVENT_ETHERNET "ethernet"
40+
41+
// Struct defining the ethernet settings
42+
typedef struct
43+
{
44+
bool staticIPConfig;
45+
IPAddress localIP;
46+
IPAddress gatewayIP;
47+
IPAddress subnetMask;
48+
IPAddress dnsIP1;
49+
IPAddress dnsIP2;
50+
bool available;
51+
} ethernet_settings_t;
52+
53+
class EthernetSettings
54+
{
55+
public:
56+
// core ethernet configuration
57+
String hostname;
58+
ethernet_settings_t ethernetSettings;
59+
60+
static void read(EthernetSettings &settings, JsonObject &root)
61+
{
62+
root["hostname"] = settings.hostname;
63+
root["static_ip_config"] = settings.ethernetSettings.staticIPConfig;
64+
JsonUtils::writeIP(root, "local_ip", settings.ethernetSettings.localIP);
65+
JsonUtils::writeIP(root, "gateway_ip", settings.ethernetSettings.gatewayIP);
66+
JsonUtils::writeIP(root, "subnet_mask", settings.ethernetSettings.subnetMask);
67+
JsonUtils::writeIP(root, "dns_ip_1", settings.ethernetSettings.dnsIP1);
68+
JsonUtils::writeIP(root, "dns_ip_2", settings.ethernetSettings.dnsIP2);
69+
ESP_LOGV(SVK_TAG, "Ethernet Settings read");
70+
}
71+
72+
static StateUpdateResult update(JsonObject &root, EthernetSettings &settings)
73+
{
74+
settings.hostname = root["hostname"] | SettingValue::format(FACTORY_ETHERNET_HOSTNAME);
75+
settings.ethernetSettings.staticIPConfig = root["static_ip_config"] | false;
76+
JsonUtils::readIP(root, "local_ip", settings.ethernetSettings.localIP);
77+
JsonUtils::readIP(root, "gateway_ip", settings.ethernetSettings.gatewayIP);
78+
JsonUtils::readIP(root, "subnet_mask", settings.ethernetSettings.subnetMask);
79+
JsonUtils::readIP(root, "dns_ip_1", settings.ethernetSettings.dnsIP1);
80+
JsonUtils::readIP(root, "dns_ip_2", settings.ethernetSettings.dnsIP2);
81+
82+
// Swap around the dns servers if 2 is populated but 1 is not
83+
if (IPUtils::isNotSet(settings.ethernetSettings.dnsIP1) && IPUtils::isSet(settings.ethernetSettings.dnsIP2))
84+
{
85+
settings.ethernetSettings.dnsIP1 = settings.ethernetSettings.dnsIP2;
86+
settings.ethernetSettings.dnsIP2 = INADDR_NONE;
87+
}
88+
89+
// Turning off static ip config if we don't meet the minimum requirements
90+
// of ipAddress and subnet. This may change to static ip only
91+
// as sensible defaults can be assumed for gateway and subnet
92+
if (settings.ethernetSettings.staticIPConfig && (IPUtils::isNotSet(settings.ethernetSettings.localIP) || IPUtils::isNotSet(settings.ethernetSettings.subnetMask)))
93+
{
94+
settings.ethernetSettings.staticIPConfig = false;
95+
}
96+
ESP_LOGV(SVK_TAG, "Ethernet Settings updated");
97+
98+
return StateUpdateResult::CHANGED;
99+
};
100+
};
101+
102+
class EthernetSettingsService : public StatefulService<EthernetSettings>
103+
{
104+
public:
105+
EthernetSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket);
106+
107+
void initEthernet();
108+
void begin();
109+
void loop();
110+
String getHostname();
111+
String getIP();
112+
113+
private:
114+
PsychicHttpServer *_server;
115+
SecurityManager *_securityManager;
116+
HttpEndpoint<EthernetSettings> _httpEndpoint;
117+
FSPersistence<EthernetSettings> _fsPersistence;
118+
EventSocket *_socket;
119+
unsigned long _lastEthernetUpdate;
120+
121+
void configureNetwork(ethernet_settings_t &network);
122+
void reconfigureEthernet();
123+
void updateEthernet();
124+
125+
};
126+
127+
#endif // end EthernetSettingsService_h

0 commit comments

Comments
 (0)