1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 04:41:24 +01:00

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.
This commit is contained in:
Asbelos 2020-09-26 12:01:00 +01:00
parent 36e6c3cd48
commit c3e17fcf04
4 changed files with 419 additions and 4 deletions

View File

@ -10,6 +10,8 @@
#include "DCCEXParser.h"
#include "version.h"
#include "WifiInterface.h"
#include "EthernetInterface.h"
#include <Arduino.h>
#endif

304
EthernetInterface.cpp Normal file
View File

@ -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 <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();
}

107
EthernetInterface.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* Ethernet Interface added by Gregor Baues
*/
#ifndef EthernetInterface_h
#define EthernetInterface_h
#include "DCCEXParser.h"
#include "MemStream.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
#include <Ethernet.h>
/* 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

View File

@ -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