/* * © 2022 Bruno Sanches * © 2021 Fred Decker * © 2020-2022 Harald Barth * © 2020-2021 Chris Harlow * © 2020 Gregor Baues * 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 . * */ #include "defines.h" #if ETHERNET_ON == true #include "EthernetInterface.h" #include "DIAG.h" #include "CommandDistributor.h" #include "WiThrottle.h" #include "DCCTimer.h" extern void looptimer(unsigned long timeout, const FSH* message); EthernetInterface * EthernetInterface::singleton=NULL; /** * @brief Setup Ethernet Connection * */ void EthernetInterface::setup() { if (singleton!=NULL) { DIAG(F("Prog Error!")); return; } if ((singleton=new EthernetInterface())) return; DIAG(F("Ethernet not initialized")); }; #ifdef IP_ADDRESS static IPAddress myIP(IP_ADDRESS); #endif /** * @brief Aquire IP Address from DHCP and start server * * @return true * @return false */ EthernetInterface::EthernetInterface() { byte mac[6]; DCCTimer::getSimulatedMacAddress(mac); connected=false; #ifdef IP_ADDRESS Ethernet.begin(mac, myIP); #else if (Ethernet.begin(mac) == 0) { DIAG(F("Ethernet.begin FAILED")); return; } #endif if (Ethernet.hardwareStatus() == EthernetNoHardware) { DIAG(F("Ethernet shield not found or W5100")); } unsigned long startmilli = millis(); while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection if (Ethernet.linkStatus() == LinkON) break; DIAG(F("Ethernet waiting for link (1sec) ")); delay(1000); } // now we either do have link of we have a W5100 // where we do not know if we have link. That's // the reason to now run checkLink. // CheckLinks sets up outboundRing if it does // not exist yet as well. checkLink(); } /** * @brief Cleanup any resources * * @return none */ EthernetInterface::~EthernetInterface() { delete server; delete outboundRing; } /** * @brief Main loop for the EthernetInterface * */ void EthernetInterface::loop() { if (!singleton || (!singleton->checkLink())) return; switch (Ethernet.maintain()) { case 1: //renewed fail DIAG(F("Ethernet Error: renewed fail")); singleton=NULL; return; case 3: //rebind fail DIAG(F("Ethernet Error: rebind fail")); singleton=NULL; return; default: //nothing happened break; } looptimer(8000, F("Ethloop after maintain")); singleton->loop2(); } /** * @brief Checks ethernet link cable status and detects when it connects / disconnects * * @return true when cable is connected, false otherwise */ bool EthernetInterface::checkLink() { if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown //if we are not connected yet, setup a new server if(!connected) { DIAG(F("Ethernet cable connected")); connected=true; #ifdef IP_ADDRESS Ethernet.setLocalIP(myIP); // for static IP, set it again #endif IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static) server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT server->begin(); LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); LCD(5,F("Port:%d"), IP_PORT); // only create a outboundRing it none exists, this may happen if the cable // gets disconnected and connected again if(!outboundRing) outboundRing=new RingStream(OUTBOUND_RING_SIZE); } return true; } else { // connected DIAG(F("Ethernet cable disconnected")); connected=false; //clean up any client for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) { if(clients[socket].connected()) clients[socket].stop(); } // tear down server delete server; server = nullptr; LCD(4,F("IP: None")); } return false; } void EthernetInterface::loop2() { if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it if (Diag::ETHERNET) DIAG(F("No outboundRing")); return; } // get client from the server EthernetClient client = server->accept(); // check for new client if (client) { if (Diag::ETHERNET) DIAG(F("Ethernet: New client ")); byte socket; for (socket = 0; socket < MAX_SOCK_NUM; socket++) { if (!clients[socket]) { // On accept() the EthernetServer doesn't track the client anymore // so we store it in our client array if (Diag::ETHERNET) DIAG(F("Socket %d"),socket); clients[socket] = client; break; } } if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW")); } // check for incoming data from all possible clients for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) { if (clients[socket]) { // read bytes from a client int count = clients[socket].read(buffer, MAX_ETH_BUFFER); looptimer(8000, F("Ethloop2 read")); if (count > 0) { if (Diag::ETHERNET) DIAG(F("Ethernet: available socket=%d,count=%d"), socket, count); buffer[count] = '\0'; // terminate the string properly if (Diag::ETHERNET) DIAG(F("buffer:%e"), buffer); // execute with data going directly back CommandDistributor::parse(socket,buffer,outboundRing); looptimer(2000, F("Ethloop2 parse")); return; // limit the amount of processing that takes place within 1 loop() cycle. } else if (count == 0) { // The client has disconnected clients[socket].stop(); CommandDistributor::forget(socket); if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket); } // fall through if count = -1 (no bytes available) } } looptimer(8000, F("Ethloop2 after incoming")); WiThrottle::loop(outboundRing); looptimer(8000, F("Ethloop after Withrottleloop")); // handle at most 1 outbound transmission int socketOut=outboundRing->read(); if (socketOut >= MAX_SOCK_NUM) { DIAG(F("Ethernet outboundRing socket=%d error"), socketOut); } else if (socketOut >= 0) { int count=outboundRing->count(); { char tmpbuf[count+1]; // one extra for '\0' for(int i=0;iread(); } tmpbuf[count]=0; if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=%d, buf:%e"), socketOut,count,tmpbuf); clients[socketOut].write(tmpbuf,count); } // do trust write does its thing and not flush // clients[socketOut].flush(); //maybe } looptimer(8000, F("Ethloop after outbound")); } #endif