Merge pull request #3049 from liamcottle/feature/ethernet

Refactor Companion Interfaces + Add ThinkNode M7 Ethernet Support
This commit is contained in:
ripplebiz
2026-07-29 23:45:51 +10:00
committed by GitHub
87 changed files with 851 additions and 412 deletions
+1
View File
@@ -14,6 +14,7 @@ public:
virtual bool isEnabled() const = 0;
virtual bool isConnected() const = 0;
virtual void loop() {};
virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
+200
View File
@@ -0,0 +1,200 @@
#pragma once
#include "BaseSerialInterface.h"
#ifndef MAX_INTERFACES
// ble, usb, wifi, ethernet
#define MAX_INTERFACES 4
#endif
enum class InterfaceType : uint8_t {
NONE,
Bluetooth,
USB,
WiFi,
Ethernet,
HardwareSerial
};
class MultiSerialInterface : public BaseSerialInterface {
private:
struct RegisteredInterface {
InterfaceType type = InterfaceType::NONE;
BaseSerialInterface* instance = nullptr;
};
bool _enabled = false;
RegisteredInterface _interfaces[MAX_INTERFACES] = {};
public:
bool addInterface(InterfaceType type, BaseSerialInterface* iface) {
// make sure an interface was provided
if(iface == nullptr){
return false;
}
// put it in the first free slot
for(int i = 0; i < MAX_INTERFACES; i++){
if(_interfaces[i].instance == nullptr){
_interfaces[i].instance = iface;
_interfaces[i].type = type;
return true;
}
}
// no free slots available
return false;
}
bool removeInterface(BaseSerialInterface* iface) {
// make sure an interface was provided
if(iface == nullptr){
return false;
}
// find and remove interface
for(int i = 0; i < MAX_INTERFACES; i++){
if(_interfaces[i].instance == iface){
_interfaces[i] = {};
return true;
}
}
// interface not found
return false;
}
void enableBluetooth() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
iface.instance->enable();
}
}
}
void disableBluetooth() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
iface.instance->disable();
}
}
}
bool isBluetoothEnabled() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
return iface.instance->isEnabled();
}
}
return false;
}
// enable all interfaces
void enable() override {
_enabled = true;
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->enable();
}
}
}
// disable all interfaces
void disable() override {
_enabled = false;
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->disable();
}
}
}
bool isEnabled() const override {
return _enabled;
}
bool isConnected() const override {
// not connected when disabled
if(!_enabled){
return false;
}
// check if any interface is connected
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isConnected()) {
return true;
}
}
// nothing connected
return false;
}
// loop all interfaces
void loop() override {
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->loop();
}
}
}
bool isWriteBusy() const override {
// not busy when disabled
if(!_enabled){
return false;
}
// check if any interface is busy
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled() && iface.instance->isWriteBusy()){
return true;
}
}
// nothing busy
return false;
}
size_t writeFrame(const uint8_t src[], size_t len) override {
// don't write when disabled or nothing provided
if(!_enabled || len == 0){
return 0;
}
// write frame to all enabled interfaces
bool allSuccessful = true;
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled()){
if(iface.instance->writeFrame(src, len) != len){
allSuccessful = false;
}
}
}
// report success if all writes completed successfully
return allSuccessful ? len : 0;
}
size_t checkRecvFrame(uint8_t dest[]) override {
// don't read when disabled
if(!_enabled){
return 0;
}
// try to read a frame from any enabled interface
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled()){
size_t frameSize = iface.instance->checkRecvFrame(dest);
if(frameSize > 0){
return frameSize;
}
}
}
// no frame received
return 0;
}
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#if defined(ETHERNET_ENABLED)
#if defined(ETHERNET_USE_CH390)
#include "helpers/ethernet/ch390/CH390EthernetInterface.h"
#elif defined(ETHERNET_USE_RAK13800)
#include "helpers/ethernet/RAK13800/RAK13800EthernetInterface.h"
#else
#error "ETHERNET_ENABLED is defined, but no specific driver flag (e.g. ETHERNET_USE_CH390) was provided!"
#endif
#endif
@@ -0,0 +1,109 @@
#include "RAK13800EthernetInterface.h"
#include "../../nrf52/EthernetMac.h"
#include <SPI.h>
#include <EthernetUdp.h>
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
bool RAK13800EthernetInterface::begin() {
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
#ifdef PIN_ETHERNET_RESET
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
#endif
// generate mac address
uint8_t mac[6];
generateEthernetMac(mac);
ETHERNET_DEBUG_PRINTLN(
"Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]);
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
// Use static IP if build flags are defined, otherwise DHCP
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gateway(ETHERNET_STATIC_GATEWAY);
IPAddress subnet(ETHERNET_STATIC_SUBNET);
IPAddress dns(ETHERNET_STATIC_DNS);
Ethernet.begin(mac, ip, dns, gateway, subnet);
#else
if (Ethernet.begin(mac) == 0) {
ETHERNET_DEBUG_PRINTLN("Failed to initialize RAK13800 hardware.");
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found.");
} else if (Ethernet.linkStatus() == LinkOFF) {
ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected.");
} else {
ETHERNET_DEBUG_PRINTLN("DHCP failed for unknown reason.");
}
return false;
}
#endif
ETHERNET_DEBUG_PRINTLN("Ethernet begin complete");
ETHERNET_DEBUG_PRINT_IP("IP Address", Ethernet.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet Mask", Ethernet.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP());
ETHERNET_DEBUG_PRINT_IP("DNS", Ethernet.dnsServerIP());
server.begin();
ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
int RAK13800EthernetInterface::available() {
return client.available();
}
int RAK13800EthernetInterface::read() {
return client.read();
}
size_t RAK13800EthernetInterface::write(const uint8_t *buf, size_t size) {
return client.write(buf, size);
}
bool RAK13800EthernetInterface::isConnected() const {
return _isConnected;
}
void RAK13800EthernetInterface::loop() {
Ethernet.maintain();
auto newClient = server.accept();
if (newClient) {
IPAddress remoteIp = newClient.remoteIP();
uint16_t remotePort = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort);
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
client = newClient;
onClientConnected();
}
_isConnected = client.connected();
}
@@ -0,0 +1,27 @@
#pragma once
#include "../SerialEthernetInterface.h"
#include <SPI.h>
#include <RAK13800_W5100S.h>
class RAK13800EthernetInterface : public SerialEthernetInterface {
bool _isConnected;
EthernetServer server;
EthernetClient client;
public:
RAK13800EthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) {
_isConnected = false;
}
bool begin();
void loop() override;
// BaseSerialInterface methods
bool isConnected() const override;
int available() override;
int read() override;
size_t write(const uint8_t *buf, size_t size) override;
};
@@ -0,0 +1,140 @@
#include "SerialEthernetInterface.h"
#define RECV_STATE_IDLE 0
#define RECV_STATE_HDR_FOUND 1
#define RECV_STATE_LEN1_FOUND 2
#define RECV_STATE_LEN2_FOUND 3
bool SerialEthernetInterface::begin() {
return true;
}
void SerialEthernetInterface::enable() {
if (_isEnabled) return;
_isEnabled = true;
clearBuffers();
}
void SerialEthernetInterface::disable() {
_isEnabled = false;
}
size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
if (len > MAX_FRAME_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len);
return 0;
}
if (isConnected() && len > 0) {
if (send_queue_len >= FRAME_QUEUE_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
return 0;
}
send_queue[send_queue_len].len = len; // add to send queue
memcpy(send_queue[send_queue_len].buf, src, len);
send_queue_len++;
return len;
}
return 0;
}
bool SerialEthernetInterface::isWriteBusy() const {
return false;
}
void SerialEthernetInterface::onClientConnected() {
_state = RECV_STATE_IDLE;
_frame_len = 0;
_rx_len = 0;
}
size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
if (isConnected()) {
if (send_queue_len > 0) { // first, check send queue
_last_write = millis();
int len = send_queue[0].len;
#if ETHERNET_RAW_LINE
ETHERNET_DEBUG_PRINTLN("TX line len=%d", len);
client.write(send_queue[0].buf, len);
client.write("\r\n", 2);
#else
uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames
pkt[0] = '>';
pkt[1] = (len & 0xFF); // LSB
pkt[2] = (len >> 8); // MSB
memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len);
ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len);
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len);
#endif
write(pkt, 3 + len);
#endif
send_queue_len--;
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
send_queue[i] = send_queue[i + 1];
}
} else {
while (available()) {
int c = read();
if (c < 0) break;
#if ETHERNET_RAW_LINE
if (c == '\r' || c == '\n') {
if (_rx_len == 0) {
continue;
}
uint16_t out_len = _rx_len;
if (out_len > MAX_FRAME_SIZE) out_len = MAX_FRAME_SIZE;
memcpy(dest, _rx_buf, out_len);
_rx_len = 0;
return out_len;
}
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
_rx_len++;
}
#else
switch (_state) {
case RECV_STATE_IDLE:
if (c == '<') {
_state = RECV_STATE_HDR_FOUND;
}
break;
case RECV_STATE_HDR_FOUND:
_frame_len = (uint8_t)c;
_state = RECV_STATE_LEN1_FOUND;
break;
case RECV_STATE_LEN1_FOUND:
_frame_len |= ((uint16_t)c) << 8;
_rx_len = 0;
_state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE;
break;
default:
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
}
_rx_len++;
if (_rx_len >= _frame_len) {
if (_frame_len > MAX_FRAME_SIZE) {
_frame_len = MAX_FRAME_SIZE;
}
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len);
#endif
memcpy(dest, _rx_buf, _frame_len);
_state = RECV_STATE_IDLE;
return _frame_len;
}
}
#endif
}
}
}
return 0;
}
@@ -1,10 +1,6 @@
#pragma once
#ifdef ETHERNET_ENABLED
#include "helpers/BaseSerialInterface.h"
#include <SPI.h>
#include <RAK13800_W5100S.h>
#include "../BaseSerialInterface.h"
#ifndef ETHERNET_TCP_PORT
#define ETHERNET_TCP_PORT 5000
@@ -12,7 +8,6 @@
// define ETHERNET_RAW_LINE=1 to use raw line-based CLI instead of framed packets
class SerialEthernetInterface : public BaseSerialInterface {
bool deviceConnected;
bool _isEnabled;
unsigned long _last_write;
uint8_t _state;
@@ -20,9 +15,6 @@ class SerialEthernetInterface : public BaseSerialInterface {
uint16_t _rx_len;
uint8_t _rx_buf[MAX_FRAME_SIZE];
EthernetServer server;
EthernetClient client;
struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
@@ -42,8 +34,7 @@ class SerialEthernetInterface : public BaseSerialInterface {
protected:
public:
SerialEthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) {
deviceConnected = false;
SerialEthernetInterface() {
_isEnabled = false;
_last_write = 0;
send_queue_len = 0;
@@ -53,6 +44,8 @@ class SerialEthernetInterface : public BaseSerialInterface {
}
bool begin();
void onClientConnected();
// BaseSerialInterface methods
void enable() override;
void disable() override;
@@ -64,7 +57,9 @@ class SerialEthernetInterface : public BaseSerialInterface {
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
void loop();
virtual int available() = 0;
virtual int read() = 0;
virtual size_t write(const uint8_t *buf, size_t size) = 0;
};
@@ -72,11 +67,9 @@ class SerialEthernetInterface : public BaseSerialInterface {
#include <Arduino.h>
#define ETHERNET_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf(name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3])
#define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf("ETH: " name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3])
#else
#define ETHERNET_DEBUG_PRINT(...) {}
#define ETHERNET_DEBUG_PRINTLN(...) {}
#define ETHERNET_DEBUG_PRINT_IP(...) {}
#endif
#endif // ETHERNET_ENABLED
@@ -0,0 +1,94 @@
#include "CH390EthernetInterface.h"
void onWiFiEvent(WiFiEvent_t event) {
switch(event){
case ARDUINO_EVENT_ETH_START:
ETHERNET_DEBUG_PRINTLN("Ethernet Started");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
ETHERNET_DEBUG_PRINTLN("Ethernet Connected");
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
ETHERNET_DEBUG_PRINTLN("Ethernet Disconnected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
ETHERNET_DEBUG_PRINTLN("Ethernet Got IP");
ETHERNET_DEBUG_PRINT_IP("IP Address", CH390.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet Mask", CH390.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", CH390.gatewayIP());
ETHERNET_DEBUG_PRINT_IP("DNS", CH390.dnsIP());
ETHERNET_DEBUG_PRINTLN("MAC Address: %s", CH390.macAddress().c_str());
break;
default:
break;
}
}
bool CH390EthernetInterface::begin() {
// listen to ethernet events
WiFi.onEvent(onWiFiEvent);
// Init CH390
ch390_config_t config = CH390_DEFAULT_CONFIG();
config.spi_miso_gpio = ETH_MISO_PIN;
config.spi_mosi_gpio = ETH_MOSI_PIN;
config.spi_sck_gpio = ETH_SCLK_PIN;
config.spi_cs_gpio = ETH_CS_PIN;
config.int_gpio = ETH_INT_PIN;
if (!CH390.begin(config)) {
ETHERNET_DEBUG_PRINTLN("Failed to initialize CH390 hardware.");
return false;
}
// Setup Static IP if build flags are present
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gw(ETHERNET_STATIC_GATEWAY);
IPAddress sn(ETHERNET_STATIC_SUBNET);
CH390.config(ip, gw, sn);
#endif
// Start Server
server.begin(ETHERNET_TCP_PORT);
ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
int CH390EthernetInterface::available() {
return client.available();
}
int CH390EthernetInterface::read() {
return client.read();
}
size_t CH390EthernetInterface::write(const uint8_t *buf, size_t size) {
return client.write(buf, size);
}
bool CH390EthernetInterface::isConnected() const {
return _isConnected;
}
void CH390EthernetInterface::loop() {
if (server.hasClient()) {
auto newClient = server.available();
if (newClient) {
IPAddress remoteIp = newClient.remoteIP();
uint16_t remotePort = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort);
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
client = newClient;
onClientConnected();
}
}
_isConnected = client.connected();
}
@@ -0,0 +1,30 @@
#pragma once
#include "../SerialEthernetInterface.h"
#include <SPI.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include <WiFiClient.h>
#include <ESP32_CH390.h>
class CH390EthernetInterface : public SerialEthernetInterface {
bool _isConnected;
WiFiServer server;
WiFiClient client;
public:
CH390EthernetInterface(){
_isConnected = false;
}
bool begin();
void loop() override;
// BaseSerialInterface methods
bool isConnected() const override;
int available() override;
int read() override;
size_t write(const uint8_t *buf, size_t size) override;
};
@@ -1,268 +0,0 @@
#ifdef ETHERNET_ENABLED
#include "SerialEthernetInterface.h"
#include "EthernetMac.h"
#include <SPI.h>
#include <EthernetUdp.h>
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
#define RECV_STATE_IDLE 0
#define RECV_STATE_HDR_FOUND 1
#define RECV_STATE_LEN1_FOUND 2
#define RECV_STATE_LEN2_FOUND 3
bool SerialEthernetInterface::begin() {
ETHERNET_DEBUG_PRINTLN("Ethernet initializing");
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
#ifdef PIN_ETHERNET_RESET
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
#endif
uint8_t mac[6];
generateEthernetMac(mac);
ETHERNET_DEBUG_PRINTLN(
"Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]);
ETHERNET_DEBUG_PRINTLN("Init");
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
// Use static IP if build flags are defined, otherwise DHCP
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gateway(ETHERNET_STATIC_GATEWAY);
IPAddress subnet(ETHERNET_STATIC_SUBNET);
IPAddress dns(ETHERNET_STATIC_DNS);
Ethernet.begin(mac, ip, dns, gateway, subnet);
#else
ETHERNET_DEBUG_PRINTLN("Begin");
if (Ethernet.begin(mac) == 0) {
ETHERNET_DEBUG_PRINTLN("Begin failed.");
// DHCP failed -- let's figure out why
if (Ethernet.hardwareStatus() == EthernetNoHardware) // Check for Ethernet hardware present.
{
ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found.");
return false;
}
if (Ethernet.linkStatus() == LinkOFF) // No physical connection
{
ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected.");
return false;
}
ETHERNET_DEBUG_PRINTLN("Ethernet: DHCP failed for unknown reason.");
return false;
}
#endif
ETHERNET_DEBUG_PRINTLN("Ethernet begin complete");
ETHERNET_DEBUG_PRINT_IP("IP", Ethernet.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet", Ethernet.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP());
server.begin(); // start listening for clients
ETHERNET_DEBUG_PRINTLN("Ethernet: listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
void SerialEthernetInterface::enable() {
if (_isEnabled) return;
_isEnabled = true;
clearBuffers();
}
void SerialEthernetInterface::disable() {
_isEnabled = false;
}
size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
if (len > MAX_FRAME_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len);
return 0;
}
if (deviceConnected && len > 0) {
if (send_queue_len >= FRAME_QUEUE_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
return 0;
}
send_queue[send_queue_len].len = len; // add to send queue
memcpy(send_queue[send_queue_len].buf, src, len);
send_queue_len++;
return len;
}
return 0;
}
bool SerialEthernetInterface::isWriteBusy() const {
return false;
}
size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
// Use accept() (not available()) so we only see newly-accepted sockets.
// available() also returns existing connected sockets that have data,
// which would cause us to treat each inbound packet as a "new client"
// and stop() the underlying socket — disconnecting the companion.
auto newClient = server.accept();
if (newClient) {
IPAddress new_ip = newClient.remoteIP();
uint16_t new_port = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN(
"New client accepted %u.%u.%u.%u:%u",
new_ip[0],
new_ip[1],
new_ip[2],
new_ip[3],
new_port);
deviceConnected = false;
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
_state = RECV_STATE_IDLE;
_frame_len = 0;
_rx_len = 0;
client = newClient;
ETHERNET_DEBUG_PRINTLN("Switched to new client");
}
if (client.connected()) {
if (!deviceConnected) {
ETHERNET_DEBUG_PRINTLN(
"Got connection %u.%u.%u.%u:%u",
client.remoteIP()[0],
client.remoteIP()[1],
client.remoteIP()[2],
client.remoteIP()[3],
client.remotePort());
deviceConnected = true;
}
} else {
if (deviceConnected) {
deviceConnected = false;
ETHERNET_DEBUG_PRINTLN("Disconnected");
}
}
if (deviceConnected) {
if (send_queue_len > 0) { // first, check send queue
_last_write = millis();
int len = send_queue[0].len;
#if ETHERNET_RAW_LINE
ETHERNET_DEBUG_PRINTLN("TX line len=%d", len);
client.write(send_queue[0].buf, len);
client.write("\r\n", 2);
#else
uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames
pkt[0] = '>';
pkt[1] = (len & 0xFF); // LSB
pkt[2] = (len >> 8); // MSB
memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len);
ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len);
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len);
#endif
client.write(pkt, 3 + len);
#endif
send_queue_len--;
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
send_queue[i] = send_queue[i + 1];
}
} else {
while (client.available()) {
int c = client.read();
if (c < 0) break;
#if ETHERNET_RAW_LINE
if (c == '\r' || c == '\n') {
if (_rx_len == 0) {
continue;
}
uint16_t out_len = _rx_len;
if (out_len > MAX_FRAME_SIZE) {
out_len = MAX_FRAME_SIZE;
}
memcpy(dest, _rx_buf, out_len);
_rx_len = 0;
return out_len;
}
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
_rx_len++;
}
#else
switch (_state) {
case RECV_STATE_IDLE:
if (c == '<') {
_state = RECV_STATE_HDR_FOUND;
}
break;
case RECV_STATE_HDR_FOUND:
_frame_len = (uint8_t)c;
_state = RECV_STATE_LEN1_FOUND;
break;
case RECV_STATE_LEN1_FOUND:
_frame_len |= ((uint16_t)c) << 8;
_rx_len = 0;
_state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE;
break;
default:
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
}
_rx_len++;
if (_rx_len >= _frame_len) {
if (_frame_len > MAX_FRAME_SIZE) {
_frame_len = MAX_FRAME_SIZE;
}
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len);
#endif
memcpy(dest, _rx_buf, _frame_len);
_state = RECV_STATE_IDLE;
return _frame_len;
}
}
#endif
}
}
}
return 0;
}
bool SerialEthernetInterface::isConnected() const {
return deviceConnected;
}
void SerialEthernetInterface::loop() {
Ethernet.maintain();
}
#endif // ETHERNET_ENABLED