mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-30 03:26:13 +01:00
305 lines
8.7 KiB
C++
305 lines
8.7 KiB
C++
|
/*
|
||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* Ethernet Interface added by Gregor Baues
|
||
|
*/
|
||
|
|
||
|
#include "EthernetInterface.h"
|
||
|
#include "DIAG.h"
|
||
|
#include "StringFormatter.h"
|
||
|
|
||
|
//#include <SPI.h>
|
||
|
#include <Ethernet.h>
|
||
|
#include <EthernetUdp.h>
|
||
|
|
||
|
|
||
|
// 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();
|
||
|
}
|