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