From c3e17fcf04b993ca26e47e500b8c7ed6a453008f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 26 Sep 2020 12:01:00 +0100 Subject: [PATCH] Add EthernetInterface (UNTESTED, UNCALLED) Thanks to Gregor....I have modified his originals to eliminate the static initialisation memory loss. These do not show up in the object code if they are not referenced. --- DCCEX.h | 2 + EthernetInterface.cpp | 304 ++++++++++++++++++++++++++++++++++++++++++ EthernetInterface.h | 107 +++++++++++++++ objdump.bat | 10 +- 4 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 EthernetInterface.cpp create mode 100644 EthernetInterface.h diff --git a/DCCEX.h b/DCCEX.h index fd118ce..091bd47 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -10,6 +10,8 @@ #include "DCCEXParser.h" #include "version.h" #include "WifiInterface.h" +#include "EthernetInterface.h" + #include #endif diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp new file mode 100644 index 0000000..3498871 --- /dev/null +++ b/EthernetInterface.cpp @@ -0,0 +1,304 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * Ethernet Interface added by Gregor Baues + */ + +#include "EthernetInterface.h" +#include "DIAG.h" +#include "StringFormatter.h" + +//#include +#include +#include + + +// Support Functions +/** + * @brief Aquire IP Address from DHCP; if that fails try a statically configured address + * + * @return true + * @return false + */ +bool EthernetInterface::setupConnection() +{ + + singleton=this; + + DIAG(F("\nInitialize Ethernet with DHCP:")); + server = EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT + ip = IPAddress(IP_ADDRESS); // init with fixed IP address needed to get to the server + connected = false; // Connection status + streamer= new MemStream(buffer, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true); // streamer who writes the results to the buffer + + if (Ethernet.begin(EthernetInterface::mac) == 0) + { + DIAG(F("\nFailed to configure Ethernet using DHCP ... Trying with fixed IP")); + Ethernet.begin(EthernetInterface::mac, EthernetInterface::ip); // default ip address + + if (Ethernet.hardwareStatus() == EthernetNoHardware) + { + DIAG(F("\nEthernet shield was not found. Sorry, can't run without hardware. :(")); + return false; + }; + if (Ethernet.linkStatus() == LinkOFF) + { + DIAG(F("\nEthernet cable is not connected.")); + return false; + } + } + + ip = Ethernet.localIP(); // reassign the obtained ip address + + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + DIAG(F("\nListening on port: [%d]"), port); + dnsip = Ethernet.dnsServerIP(); + DIAG(F("\nDNS server IP address: [%d.%d.%d.%d] "), ip[0], ip[1], ip[2], ip[3]); + return true; +} + +/** + * @brief Handles command requests recieved via UDP. UDP is a connection less, unreliable protocol as it doesn't maintain state but fast. + * + */ + void EthernetInterface::udpHandler() { + singleton->udpHandler2(); + } + void EthernetInterface::udpHandler2() +{ + + int packetSize = Udp.parsePacket(); + if (packetSize) + { + DIAG(F("\nReceived packet of size:[%d]\n"), packetSize); + IPAddress remote = Udp.remoteIP(); + DIAG(F("From: [%d.%d.%d.%d:"), remote[0], remote[1], remote[2], remote[3]); + char portBuffer[6]; + DIAG(F("%s]\n"), utoa(Udp.remotePort(), portBuffer, 10)); // DIAG has issues with unsigend int's so go through utoa + + // read the packet into packetBufffer + Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); + + DIAG(F("Command: [%s]\n"), packetBuffer); + + streamer->flush(); + + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + + ethParser.parse(streamer, (byte *)packetBuffer, true); // set to true so it is sync cf. WifiInterface + + if (streamer->available() == 0) + { + DIAG(F("\nNo response\n")); + } + else + { + // send the reply + DIAG(F("Response: %s\n"), (char *)buffer); + Udp.write((char *)buffer); + Udp.endPacket(); + } + + memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE); // reset PacktBuffer + return; + } +} + +/** + * @brief Handles command requests recieved via TCP. Supports up to the max# of simultaneous requests which is 8. The connection gets closed as soon as we finished processing + * + */ + void EthernetInterface::tcpHandler() +{ + singleton->tcpHandler2(); +} + void EthernetInterface::tcpHandler2() +{ + // get client from the server + EthernetClient client = getServer().accept(); + + // check for new client + if (client) + { + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (!clients[i]) + { + // On accept() the EthernetServer doesn't track the client anymore + // so we store it in our client array + clients[i] = client; + break; + } + } + } + + // check for incoming data from all possible clients + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && clients[i].available() > 0) + { + // read bytes from a client + int count = clients[i].read(buffer, MAX_ETH_BUFFER); + buffer[count] = '\0'; // terminate the string properly + DIAG(F("\nReceived packet of size:[%d]\n"), count); + DIAG(F("From Client #: [%d]\n"), i); + DIAG(F("Command: [%s]\n"), buffer); + + // as we use buffer for recv and send we have to reset the write position + streamer->setBufferContentPosition(0, 0); + + ethParser.parse(streamer, buffer, true); // set to true to that the execution in DCC is sync + + if (streamer->available() == 0) + { + DIAG(F("No response\n")); + } + else + { + buffer[streamer->available()] = '\0'; // mark end of buffer, so it can be used as a string later + DIAG(F("Response: %s\n"), (char *)buffer); + if (clients[i].connected()) + { + clients[i].write(buffer, streamer->available()); + } + } + } + // stop any clients which disconnect + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && !clients[i].connected()) + { + DIAG(F("Disconnect client #%d \n"), i); + clients[i].stop(); + } + } + } +} + +// Class Functions +/** + * @brief Setup Ethernet Connection + * + * @param pt Protocol used + * @param localPort Port number for the connection + */ +void EthernetInterface::setup(protocolType pt, uint16_t localPort) +{ + DIAG(F("\n++++++ Ethernet Setup In Progress ++++++++\n")); + port = localPort; + if (setupConnection()) + { + DIAG(F("\nProtocol: [%s]\n"), pt ? "UDP" : "TCP"); + switch (pt) + { + case UDP: + { + if (Udp.begin(localPort)) + { + connected = true; + protocolHandler = udpHandler; + } + else + { + DIAG(F("\nUDP client failed to start")); + connected = false; + } + break; + }; + case TCP: + { + Ethernet.begin(mac, ip); + EthernetServer server(localPort); + setServer(server); + server.begin(); + connected = true; + protocolHandler = tcpHandler; + break; + }; + default: + { + DIAG(F("Unkown Ethernet protocol; Setup failed")); + connected = false; + return; + } + } + } + else + { + connected = false; + }; + DIAG(F("\n++++++ Ethernet Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED")); +}; + +/** + * @brief Setup Ethernet on default port and user choosen protocol + * + * @param pt Protocol UDP or TCP + */ +void EthernetInterface::setup(protocolType pt) +{ + setup(pt, LISTEN_PORT); +}; + +/** + * @brief Ethernet setup with defaults TCP / Listen Port + * + */ +void EthernetInterface::setup() +{ + setup(TCP, LISTEN_PORT); +} + +/** + * @brief Main loop for the EthernetInterface + * + */ +void EthernetInterface::loop() +{ + switch (Ethernet.maintain()) + { + case 1: + //renewed fail + DIAG(F("\nError: renewed fail")); + break; + + case 2: + //renewed success + DIAG(F("\nRenewed success: ")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"),ip[0], ip[1], ip[2], ip[3]); + break; + + case 3: + //rebind fail + DIAG(F("Error: rebind fail")); + break; + + case 4: + //rebind success + DIAG(F("Rebind success")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + break; + + default: + //nothing happened + break; + } + protocolHandler(); +} diff --git a/EthernetInterface.h b/EthernetInterface.h new file mode 100644 index 0000000..b702f04 --- /dev/null +++ b/EthernetInterface.h @@ -0,0 +1,107 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * Ethernet Interface added by Gregor Baues + */ + +#ifndef EthernetInterface_h +#define EthernetInterface_h + +#include "DCCEXParser.h" +#include "MemStream.h" +#include +#include +#include + +/* some generated mac addresses as EthernetShields don't have one by default in HW. + * Sometimes they come on a sticker on the EthernetShield then use this address otherwise + * just choose one from below or generate one yourself. Only condition is that there is no + * other device on your network with the same Mac address. + * + * 52:b8:8a:8e:ce:21 + * e3:e9:73:e1:db:0d + * 54:2b:13:52:ac:0c + * c2:d8:d4:7d:7c:cb + * 86:cf:fa:9f:07:79 + */ + +/** + * @brief Network Configuration + * + */ +#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above +#define IP_ADDRESS 10, 0, 0, 101 // Just in case we don't get an adress from DHCP try a static one; make sure + // this one is not used elsewhere and corresponds to your network layout +#define LISTEN_PORT 3366 // default listen port for the server +#define MAX_ETH_BUFFER 250 + +typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); + +enum protocolType { + TCP, + UDP +}; + +typedef void (*protocolCallback)(); + +class EthernetInterface { + +private: + EthernetServer server; + + public: + DCCEXParser ethParser; + bool connected; + byte mac[6]; + IPAddress ip; + uint16_t port; + IPAddress dnsip; + + void setup(protocolType pt, uint16_t lp); // specific port nummber + void setup(protocolType pt); // uses default port number + void setup(); // all defaults (protocol/port) + + protocolCallback protocolHandler; + + void loop(); + + private: + static EthernetInterface * singleton; + + char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; // buffer to hold incoming UDP packet, + uint8_t buffer[MAX_ETH_BUFFER]; // buffer provided to the streamer to be filled with the reply (used by TCP also for the recv) + MemStream * streamer; // streamer who writes the results to the buffer + EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield + + bool setupConnection(); + static void udpHandler(); + static void tcpHandler(); + void udpHandler2(); + void tcpHandler2(); + EthernetUDP Udp; + + EthernetServer getServer() { + return server; + }; + void setServer(EthernetServer s) { + server = s; + }; +}; + +#endif diff --git a/objdump.bat b/objdump.bat index bee2d0f..af30cb5 100644 --- a/objdump.bat +++ b/objdump.bat @@ -1,12 +1,14 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt -avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt +SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf + +avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt notepad %TMP%\OBJDUMP_%a%.txt EXIT