diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 798fb3d5a..ba98a6b1f 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -3,7 +3,7 @@ extends = arduino_base platform = platformio/espressif32@^5.2.0 build_src_filter = - ${arduino_base.build_src_filter} - - - + ${arduino_base.build_src_filter} - - - - upload_speed = 921600 debug_init_break = tbreak setup monitor_filters = esp32_exception_decoder @@ -33,8 +33,7 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd h2zero/NimBLE-Arduino@^1.4.0 - arduino-libraries/NTPClient@^3.1.0 - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 lib_ignore = segger_rtt diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 8dc6d0b62..f5338d9a9 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -2,7 +2,7 @@ extends = arduino_base platform = platformio/espressif32@^5.2.0 build_src_filter = - ${arduino_base.build_src_filter} - - - + ${arduino_base.build_src_filter} - - - - upload_speed = 961200 monitor_speed = 115200 debug_init_break = tbreak setup @@ -33,7 +33,6 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd h2zero/NimBLE-Arduino@^1.4.0 - arduino-libraries/NTPClient@^3.1.0 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 lib_ignore = diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index f4e2af236..46f946530 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -8,7 +8,7 @@ build_flags = ${arduino_base.build_flags} -Wno-unused-variable -Isrc/platform/nrf52 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index eb65322d4..b61071007 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -7,7 +7,8 @@ build_src_filter = - - - - - + - + - - - +<../variants/portduino> diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini index 9eea340bf..8b7bdbff9 100644 --- a/arch/rp2040/rp2040.ini +++ b/arch/rp2040/rp2040.ini @@ -10,7 +10,7 @@ build_flags = -D__PLAT_RP2040__ # -D _POSIX_THREADS build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - lib_ignore = BluetoothOTA lib_deps = diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini index d13750fdb..3fc7583ad 100644 --- a/arch/stm32/stm32wl5e.ini +++ b/arch/stm32/stm32wl5e.ini @@ -10,7 +10,7 @@ build_flags = # Arduino/PlatformIO framework-arduinoststm32 package does not presently have SUBGHZSPI support # -DPIN_SPI_MOSI=PINSUBGHZSPIMOSI -DPIN_SPI_MISO=PINSUBGHZSPIMISO -DPIN_SPI_SCK=PINSUBGHZSPISCK build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - lib_deps = ${env.lib_deps} https://github.com/kokke/tiny-AES-c.git#f06ac37fc31dfdaca2e0d9bec83f90d5663c319b diff --git a/platformio.ini b/platformio.ini index 1f97cfb19..527bce0d0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,6 +79,7 @@ build_src_filter = ${env.build_src_filter} - [networking_base] lib_deps = knolleary/PubSubClient@^2.8 + arduino-libraries/NTPClient@^3.1.0 meshtastic/json11@^1.0.2 ; Common libs for environmental measurements in telemetry module diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index de9b95027..66c83171a 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -44,11 +44,9 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg) static char printBuf[160]; va_copy(copy, arg); - int len = vsnprintf(printBuf, sizeof(printBuf), format, copy); + size_t len = vsnprintf(printBuf, sizeof(printBuf), format, copy); va_end(copy); - if (len < 0) return 0; - // If the resulting string is longer than sizeof(printBuf)-1 characters, the remaining characters are still counted for the return value if (len > sizeof(printBuf) - 1) { diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp index 059630bfe..f8314fe51 100644 --- a/src/buzz/buzz.cpp +++ b/src/buzz/buzz.cpp @@ -1,5 +1,6 @@ #include "buzz.h" #include "configuration.h" +#include "NodeDB.h" #ifndef PIN_BUZZER @@ -42,17 +43,19 @@ const int DURATION_1_8 = 125; // 1/8 note const int DURATION_1_4 = 250; // 1/4 note void playTones(const ToneDuration *tone_durations, int size) { - for (int i = 0; i < size; i++) { - const auto &tone_duration = tone_durations[i]; + if (config.network.eth_enabled != true) { + for (int i = 0; i < size; i++) { + const auto &tone_duration = tone_durations[i]; #ifdef M5STACK - Tone.tone(tone_duration.frequency_khz); - delay(tone_duration.duration_ms); - Tone.mute(); + Tone.tone(tone_duration.frequency_khz); + delay(tone_duration.duration_ms); + Tone.mute(); #else - tone(PIN_BUZZER, tone_duration.frequency_khz, tone_duration.duration_ms); + tone(PIN_BUZZER, tone_duration.frequency_khz, tone_duration.duration_ms); #endif - // to distinguish the notes, set a minimum time between them. - delay(1.3 * tone_duration.duration_ms); + // to distinguish the notes, set a minimum time between them. + delay(1.3 * tone_duration.duration_ms); + } } } diff --git a/src/configuration.h b/src/configuration.h index 9374c29d4..6ad5f8ff9 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -142,6 +142,9 @@ along with this program. If not, see . #ifndef HAS_WIFI #define HAS_WIFI 0 #endif +#ifndef HAS_ETHERNET + #define ETHERNET 0 +#endif #ifndef HAS_SCREEN #define HAS_SCREEN 0 #endif diff --git a/src/main.cpp b/src/main.cpp index d40a1bdf0..d2505d816 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,7 @@ // #include #include "mesh/http/WiFiAPClient.h" +#include "mesh/eth/ethClient.h" #ifdef ARCH_ESP32 #include "mesh/http/WebServer.h" @@ -41,6 +42,11 @@ #include "mqtt/MQTT.h" #endif +#if HAS_ETHERNET +#include "mesh/eth/ethServerAPI.h" +#include "mqtt/MQTT.h" +#endif + #include "LLCC68Interface.h" #include "RF95Interface.h" #include "SX1262Interface.h" @@ -278,11 +284,12 @@ void setup() #ifdef ARCH_NRF52 nrf52Setup(); #endif - playStartMelody(); // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu iniot (esp32setup), because we need the random seed set nodeDB.init(); + playStartMelody(); + // Currently only the tbeam has a PMU power = new Power(); power->setStatusHandler(powerStatus); @@ -439,13 +446,18 @@ void setup() } #endif -#if HAS_WIFI +#if HAS_WIFI || HAS_ETHERNET mqttInit(); #endif +#ifndef ARCH_PORTDUINO // Initialize Wifi initWifi(forceSoftAP); + // Initialize Ethernet + initEthernet(); +#endif + #ifdef ARCH_ESP32 // Start web server thread. webServerThread = new WebServerThread(); diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp new file mode 100644 index 000000000..1bd8ea96e --- /dev/null +++ b/src/mesh/eth/ethClient.cpp @@ -0,0 +1,166 @@ +#include "mesh/eth/ethClient.h" +#include "NodeDB.h" +#include "RTC.h" +#include "concurrency/Periodic.h" +#include +#include +#include "target_specific.h" +#include "mesh/eth/ethServerAPI.h" +#include "mqtt/MQTT.h" + +#ifndef DISABLE_NTP +#include + +// NTP +EthernetUDP ntpUDP; + +NTPClient timeClient(ntpUDP, config.network.ntp_server); + +uint32_t ntp_renew = 0; +#endif + +// Stores our hostname +char ourHost[16]; + +bool ethStartupComplete = 0; + +using namespace concurrency; + +static Periodic *ethEvent; + +static int32_t reconnectETH() +{ + if (config.network.eth_enabled) { + Ethernet.maintain(); + if (!ethStartupComplete) { + // Start web server + DEBUG_MSG("... Starting network services\n"); + + // // start mdns + // if (!MDNS.begin("Meshtastic")) { + // DEBUG_MSG("Error setting up MDNS responder!\n"); + // } else { + // DEBUG_MSG("mDNS responder started\n"); + // DEBUG_MSG("mDNS Host: Meshtastic.local\n"); + // MDNS.addService("http", "tcp", 80); + // MDNS.addService("https", "tcp", 443); + // } + + #ifndef DISABLE_NTP + DEBUG_MSG("Starting NTP time client\n"); + timeClient.begin(); + timeClient.setUpdateInterval(60 * 60); // Update once an hour + #endif + + // initWebServer(); + initApiServer(); + + ethStartupComplete = true; + } + + // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' + if (mqtt && !mqtt->connected()) { + mqtt->reconnect(); + } + } + +#ifndef DISABLE_NTP + if (isEthernetAvailable() && (ntp_renew < millis())) { + DEBUG_MSG("Updating NTP time\n"); + if (timeClient.update()) { + DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n"); + + struct timeval tv; + tv.tv_sec = timeClient.getEpochTime(); + tv.tv_usec = 0; + + perhapsSetRTC(RTCQualityNTP, &tv); + + ntp_renew = millis() + 43200 * 1000; // every 12 hours + + } else { + DEBUG_MSG("NTP Update failed\n"); + } + } +#endif + + return 5000; // every 5 seconds +} + +// Startup Ethernet +bool initEthernet() +{ + + // config.network.eth_enabled = true; + // config.network.eth_mode = Config_NetworkConfig_EthMode_DHCP; + + if (config.network.eth_enabled) { + +#ifdef PIN_ETHERNET_RESET + pinMode(PIN_ETHERNET_RESET, OUTPUT); + digitalWrite(PIN_ETHERNET_RESET, LOW); // Reset Time. + delay(100); + digitalWrite(PIN_ETHERNET_RESET, HIGH); // Reset Time. +#endif + + Ethernet.init( ETH_SPI_PORT, PIN_ETHERNET_SS ); + + uint8_t mac[6]; + + int status = 0; + + // createSSLCert(); + + getMacAddr(mac); // FIXME use the BLE MAC for now... + + if (config.network.eth_mode == Config_NetworkConfig_EthMode_DHCP) { + DEBUG_MSG("starting Ethernet DHCP\n"); + status = Ethernet.begin(mac); + } else if (config.network.eth_mode == Config_NetworkConfig_EthMode_STATIC) { + DEBUG_MSG("starting Ethernet Static\n"); + Ethernet.begin(mac, config.network.eth_config.ip, config.network.eth_config.dns, config.network.eth_config.subnet); + } else { + DEBUG_MSG("Ethernet Disabled\n"); + return false; + } + + if (status == 0) { + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + DEBUG_MSG("Ethernet shield was not found.\n"); + return false; + } else if (Ethernet.linkStatus() == LinkOFF) { + DEBUG_MSG("Ethernet cable is not connected.\n"); + return false; + } else{ + DEBUG_MSG("Unknown Ethernet error.\n"); + return false; + } + } else { + DEBUG_MSG("Local IP %u.%u.%u.%u\n",Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]); + DEBUG_MSG("Subnet Mask %u.%u.%u.%u\n",Ethernet.subnetMask()[0], Ethernet.subnetMask()[1], Ethernet.subnetMask()[2], Ethernet.subnetMask()[3]); + DEBUG_MSG("Gateway IP %u.%u.%u.%u\n",Ethernet.gatewayIP()[0], Ethernet.gatewayIP()[1], Ethernet.gatewayIP()[2], Ethernet.gatewayIP()[3]); + DEBUG_MSG("DNS Server IP %u.%u.%u.%u\n",Ethernet.dnsServerIP()[0], Ethernet.dnsServerIP()[1], Ethernet.dnsServerIP()[2], Ethernet.dnsServerIP()[3]); + } + + ethEvent = new Periodic("ethConnect", reconnectETH); + + return true; + + } else { + DEBUG_MSG("Not using Ethernet\n"); + return false; + } +} + +bool isEthernetAvailable() { + + if (!config.network.eth_enabled) { + return false; + } else if (Ethernet.hardwareStatus() == EthernetNoHardware) { + return false; + } else if (Ethernet.linkStatus() == LinkOFF) { + return false; + } else { + return true; + } +} diff --git a/src/mesh/eth/ethClient.h b/src/mesh/eth/ethClient.h new file mode 100644 index 000000000..9e1745b9f --- /dev/null +++ b/src/mesh/eth/ethClient.h @@ -0,0 +1,8 @@ +#pragma once + +#include "configuration.h" +#include +#include + +bool initEthernet(); +bool isEthernetAvailable(); diff --git a/src/mesh/eth/ethServerAPI.cpp b/src/mesh/eth/ethServerAPI.cpp new file mode 100644 index 000000000..bb7dd927d --- /dev/null +++ b/src/mesh/eth/ethServerAPI.cpp @@ -0,0 +1,82 @@ +#include "ethServerAPI.h" +#include "configuration.h" +#include + +static ethServerPort *apiPort; + +void initApiServer(int port) +{ + // Start API server on port 4403 + if (!apiPort) { + apiPort = new ethServerPort(port); + DEBUG_MSG("API server listening on TCP port %d\n", port); + apiPort->init(); + } +} + +ethServerAPI::ethServerAPI(EthernetClient &_client) : StreamAPI(&client), client(_client) +{ + DEBUG_MSG("Incoming ethernet connection\n"); +} + +ethServerAPI::~ethServerAPI() +{ + client.stop(); + + // FIXME - delete this if the client dropps the connection! +} + +/// override close to also shutdown the TCP link +void ethServerAPI::close() +{ + client.stop(); // drop tcp connection + StreamAPI::close(); +} + +/// Check the current underlying physical link to see if the client is currently connected +bool ethServerAPI::checkIsConnected() +{ + return client.connected(); +} + +int32_t ethServerAPI::runOnce() +{ + if (client.connected()) { + return StreamAPI::runOnce(); + } else { + DEBUG_MSG("Client dropped connection, suspending API service\n"); + enabled = false; // we no longer need to run + return 0; + } +} + +/// If an api server is running, we try to spit out debug 'serial' characters there +void ethServerPort::debugOut(char c) +{ + if (apiPort && apiPort->openAPI) + apiPort->openAPI->debugOut(c); +} + + +ethServerPort::ethServerPort(int port) : EthernetServer(port), concurrency::OSThread("ApiServer") {} + +void ethServerPort::init() +{ + begin(); +} + +int32_t ethServerPort::runOnce() +{ + auto client = available(); + if (client) { + // Close any previous connection (see FIXME in header file) + if (openAPI) { + DEBUG_MSG("Force closing previous TCP connection\n"); + delete openAPI; + } + + openAPI = new ethServerAPI(client); + } + + return 100; // only check occasionally for incoming connections +} diff --git a/src/mesh/eth/ethServerAPI.h b/src/mesh/eth/ethServerAPI.h new file mode 100644 index 000000000..c92ab2f17 --- /dev/null +++ b/src/mesh/eth/ethServerAPI.h @@ -0,0 +1,58 @@ +#pragma once + +#include "StreamAPI.h" +#include + +/** + * Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs + * (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs). + */ +class ethServerAPI : public StreamAPI +{ + private: + EthernetClient client; + + public: + explicit ethServerAPI(EthernetClient &_client); + + virtual ~ethServerAPI(); + + /// override close to also shutdown the TCP link + virtual void close(); + + protected: + /// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to + /// stay in the POWERED state to prevent disabling wifi) + virtual void onConnectionChanged(bool connected) override {} + + virtual int32_t runOnce() override; // Check for dropped client connections + + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override; +}; + +/** + * Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed + */ +class ethServerPort : public EthernetServer, private concurrency::OSThread +{ + /** The currently open port + * + * FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to + * delegate to the worker. Once coroutines are implemented we can relax this restriction. + */ + ethServerAPI *openAPI = NULL; + + public: + explicit ethServerPort(int port); + + void init(); + + /// If an api server is running, we try to spit out debug 'serial' characters there + static void debugOut(char c); + + protected: + int32_t runOnce() override; +}; + +void initApiServer(int port=4403); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 7deba4668..e77e82e54 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -8,7 +8,9 @@ #include "mesh/generated/mqtt.pb.h" #include "mesh/generated/telemetry.pb.h" #include "sleep.h" +#if HAS_WIFI #include +#endif #include #include @@ -105,6 +107,11 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient) // preflightSleepObserver.observe(&preflightSleep); } +bool MQTT::connected() +{ + return pubSub.connected(); +} + void MQTT::reconnect() { if (wantsLink()) { @@ -189,7 +196,13 @@ bool MQTT::wantsLink() const } } +#if HAS_WIFI return hasChannel && WiFi.isConnected(); +#endif +#if HAS_ETHERNET + return hasChannel && (Ethernet.linkStatus() == LinkON); +#endif + return false; } int32_t MQTT::runOnce() @@ -346,9 +359,9 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) msgPayload = Json::object{ {"time", (int)decoded->time}, {"pos_timestamp", (int)decoded->timestamp}, - {"latitude_i", decoded->latitude_i}, - {"longitude_i", decoded->longitude_i}, - {"altitude", decoded->altitude} + {"latitude_i", (int)decoded->latitude_i}, + {"longitude_i", (int)decoded->longitude_i}, + {"altitude", (int)decoded->altitude} }; } else { DEBUG_MSG("Error decoding protobuf for position message!\n"); @@ -371,8 +384,8 @@ std::string MQTT::downstreamPacketToJson(MeshPacket *mp) {"description", decoded->description}, {"expire", (int)decoded->expire}, {"locked", decoded->locked}, - {"latitude_i", decoded->latitude_i}, - {"longitude_i", decoded->longitude_i}, + {"latitude_i", (int)decoded->latitude_i}, + {"longitude_i", (int)decoded->longitude_i}, }; } else { DEBUG_MSG("Error decoding protobuf for position message!\n"); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 9d80c7d91..c8381574c 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -5,7 +5,12 @@ #include "concurrency/OSThread.h" #include "mesh/Channels.h" #include +#if HAS_WIFI #include +#endif +#if HAS_ETHERNET +#include +#endif /** * Our wrapper/singleton for sending/receiving MQTT "udp" packets. This object isolates the MQTT protocol implementation from @@ -16,7 +21,12 @@ class MQTT : private concurrency::OSThread // supposedly the current version is busted: // http://www.iotsharing.com/2017/08/how-to-use-esp32-mqtts-with-mqtts-mosquitto-broker-tls-ssl.html // WiFiClientSecure wifiClient; +#if HAS_WIFI WiFiClient mqttClient; +#endif +#if HAS_ETHERNET + EthernetClient mqttClient; +#endif PubSubClient pubSub; // instead we supress sleep from our runOnce() callback @@ -38,6 +48,8 @@ class MQTT : private concurrency::OSThread /** Attempt to connect to server if necessary */ void reconnect(); + + bool connected(); protected: virtual int32_t runOnce() override; diff --git a/src/network-stubs.cpp b/src/network-stubs.cpp new file mode 100644 index 000000000..b7b4c146f --- /dev/null +++ b/src/network-stubs.cpp @@ -0,0 +1,27 @@ +#include "configuration.h" + +#if (HAS_WIFI == 0) + +bool initWifi(bool forceSoftAP) { + return false; +} + +void deinitWifi() {} + +bool isWifiAvailable() { + return false; +} + +#endif + +#if (HAS_ETHERNET == 0) + +bool initEthernet() { + return false; +} + +bool isEthernetAvailable() { + return false; +} + +#endif diff --git a/src/wifi-stubs.cpp b/src/wifi-stubs.cpp deleted file mode 100644 index faf58b620..000000000 --- a/src/wifi-stubs.cpp +++ /dev/null @@ -1,19 +0,0 @@ -//#include "mesh/wifi/WebServer.h" -#include "configuration.h" - -#ifndef ARCH_ESP32 - -//#include "mesh/wifi/WiFiAPClient.h" - -bool initWifi(bool forceSoftAP) { - return false; -} - -void deinitWifi() {} - -bool isWifiAvailable() -{ - return false; -} - -#endif diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index d8e2ba3e9..fccffb51f 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -3,10 +3,12 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + lib_deps = ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} melopero/Melopero RV3028@^1.1.0 + https://github.com/caveman99/RAK13800-W5100S.git#main debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink \ No newline at end of file diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index b42163fbb..904491b6a 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -229,6 +229,12 @@ static const uint8_t SCK = PIN_SPI_SCK; #define HAS_RTC 1 +#define HAS_ETHERNET 1 + +#define PIN_ETHERNET_RESET 21 +#define PIN_ETHERNET_SS PIN_EINK_CS +#define ETH_SPI_PORT SPI1 + #ifdef __cplusplus } #endif