From d00864cbf43395b11ec433c19e276ef85b53352e Mon Sep 17 00:00:00 2001 From: Gregor Baues Date: Fri, 23 Oct 2020 21:30:56 +0200 Subject: [PATCH] NetworkInterface integrated --- CommandStation-EX.ino | 2 +- DIAG copy.h | 23 ----- EthernetSetup.cpp | 2 +- NetworkInterface.cpp | 2 - Transport.h | 4 +- TransportProcessor.cpp | 217 +++++++++++++++++++++++++++++++---------- TransportProcessor.h | 49 +++++----- 7 files changed, 196 insertions(+), 103 deletions(-) delete mode 100644 DIAG copy.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 1c9c226..0fd0248 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -64,7 +64,7 @@ void setup() DIAG(F("\nFree RAM before network init: [%d]\n"),freeMemory()); DIAG(F("\nNetwork Setup In Progress ...\n")); - NetworkInterface::setup(WIFI, TCP, 8888); // specify WIFI or ETHERNET depending on if you have Wifi or an EthernetShield; Wifi has to be on Serial1 UDP or TCP for the protocol + NetworkInterface::setup(ETHERNET, TCP, 8888); // specify WIFI or ETHERNET depending on if you have Wifi or an EthernetShield; Wifi has to be on Serial1 UDP or TCP for the protocol NetworkInterface::setHttpCallback(httpRequestHandler); // The network interface will provide and HTTP request object which can be used as well to send the reply. cf. example above // NetworkInterface::setup(WIFI, UDP, 8888); // Setup without port will use the by default port 2560 :: Wifi+UDP IS NOT YET SUPPORTED diff --git a/DIAG copy.h b/DIAG copy.h deleted file mode 100644 index e0d4383..0000000 --- a/DIAG copy.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * © 2020, Chris Harlow. All rights reserved. - * - * This file is part of Asbelos DCC API - * - * 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 . - */ -#ifndef DIAG_h -#define DIAG_h -#include "StringFormatter.h" -#define DIAG StringFormatter::diag -#endif diff --git a/EthernetSetup.cpp b/EthernetSetup.cpp index 0955dbe..ac84bd2 100644 --- a/EthernetSetup.cpp +++ b/EthernetSetup.cpp @@ -54,7 +54,7 @@ EthernetServer* EthernetSetup::setup() } else if (Ethernet.hardwareStatus() == EthernetW5500) { - DIAG(F("W5500 Ethernet controller detected.")); + DIAG(F("\nW5500 Ethernet controller detected.")); maxConnections = 8; } diff --git a/NetworkInterface.cpp b/NetworkInterface.cpp index 413ebce..9e654f9 100644 --- a/NetworkInterface.cpp +++ b/NetworkInterface.cpp @@ -19,8 +19,6 @@ #include "DIAG.h" #include "NetworkInterface.h" -// #include "Singelton.h" -// #include "WifiTransport.h" #include "EthernetSetup.h" #include "WifiSetup.h" diff --git a/Transport.h b/Transport.h index 1452302..191558f 100644 --- a/Transport.h +++ b/Transport.h @@ -23,7 +23,7 @@ #include #include -#include +#include "NetworkInterface.h" #include "TransportProcessor.h" @@ -80,4 +80,4 @@ public: }; -#endif // !Transport_h \ No newline at end of file +#endif // !Transport_h diff --git a/TransportProcessor.cpp b/TransportProcessor.cpp index 13cf64f..9fd9ddc 100644 --- a/TransportProcessor.cpp +++ b/TransportProcessor.cpp @@ -16,15 +16,20 @@ */ #include - #include "DIAG.h" -// #include "DCCEXParser.h" #include "NetworkInterface.h" #include "HttpRequest.h" #include "TransportProcessor.h" -// DCCEXParser ethParser; +#ifdef DCCEX_ENABLED + + #include "DCCEXParser.h" + #include "MemStream.h" + + DCCEXParser ethParser; + +#endif static uint8_t buffer[MAX_ETH_BUFFER]; static char command[MAX_JMRI_CMD] = {0}; @@ -38,6 +43,76 @@ char protocolName[4][11] = {"JMRI", "WITHROTTLE", "HTTP", "UNKNOWN"}; // change bool diagNetwork = false; uint8_t diagNetworkClient = 0; +#ifdef DCCEX_ENABLED +/** + * @brief Sending a reply by using the StringFormatter (this will result in every byte send individually which may/will create an important Network overhead). + * Here we hook back into the DCC code for actually processing the command using a DCCParser. Alternatively we could use MemeStream in order to build the entiere reply + * before ending it. + * + * @param stream Actually the Client to whom to send the reply. As Clients implement Print this is working + * @param command The reply to be send ( echo as in sendReply() ) + * @param blocking if set to true will instruct the DCC code to not use the async callback functions + */ + +void sendToDCC(Connection* c ,char *command, bool blocking) +{ + static MemStream* streamer = new MemStream((byte *)command, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true); + + DIAG(F("DCC parsing: [%e]\n"), command); + // as we use buffer for recv and send we have to reset the write position + streamer->setBufferContentPosition(0, 0); + + ethParser.parse(streamer, (byte *)command, true); // set to true to that the execution in DCC is sync + + if (streamer->available() == 0) + { + DIAG(F("No response\n")); + } + else + { + command[streamer->available()] = '\0'; // mark end of buffer, so it can be used as a string later + DIAG(F("Response: %s\n"), command); + if (c->client->connected()) + { + c->client->write((byte *)command, streamer->available()); + } + } +} +#else +/** + * @brief Sending a reply without going through the StringFormatter. Sends the repy in one go + * + * @param client Client who send the command to which the reply shall be send + * @param command Command initaliy recieved to be echoed back + */ +void sendReply(Connection* c, char *command) +{ + char *number; + char seqNumber[6]; + int i = 0; + + memset(reply, 0, MAX_ETH_BUFFER); // reset reply + + number = strrchr(command, ':'); // replace the int after the last ':' + while( &command[i] != number ) { // copy command into the reply upto the last ':' + reply[i] = command[i]; + i++; + } + + strcat((char *)reply, ":"); + itoa(_sseq[c->id], seqNumber, 10); + strcat((char *)reply, seqNumber); + strcat((char *)reply, ">"); + + DIAG(F("Response: [%e]"), (char *)reply); + if (c->client->connected()) + { + c->client->write(reply, strlen((char *)reply)); + _sseq[c->id]++; + DIAG(F(" send\n")); + } +}; +#endif /** * @brief creates a HttpRequest object for the user callback. Some conditions apply esp reagrding the length of the items in the Request @@ -48,6 +123,7 @@ uint8_t diagNetworkClient = 0; */ void httpProcessor(Connection* c) { + if (httpReq.callback == 0) return; // no callback i.e. nothing to do /** * @todo look for jmri formatted uris and execute those if there is no callback. If no command found ignore and @@ -163,6 +239,8 @@ appProtocol setAppProtocol(char a, char b, Connection *c) * @brief Parses the buffer to extract commands to be executed * */ + +// void TransportProcessor::processStream(Connection *c) void processStream(Connection *c) { uint8_t i, j, k, l = 0; @@ -181,18 +259,22 @@ void processStream(Connection *c) // check if there is again an overflow and copy if needed if ((i = strlen((char *)buffer)) == MAX_ETH_BUFFER - 1) - { + { // only then we shall be in an overflow situation + // DIAG(F("\nPossible overflow situation detected: %d "), i); j = i; while (buffer[i] != c->delimiter) - { + { // what if there is none: ? + // DIAG(F("%c"),(char) buffer[i]); i--; } - i++; // start of the buffer to copy + i++; // start of the buffer to copy l = i; - k = j - i; // length to copy + k = j - i; // length to copy + for (j = 0; j < k; j++, i++) { c->overflow[j] = buffer[i]; + // DIAG(F("\n%d %d %d %c"),k,j,i, buffer[i]); // c->overflow[j]); } buffer[l] = '\0'; // terminate buffer just after the last '>' // DIAG(F("\nNew buffer: [%s] New overflow: [%s]\n"), (char*) buffer, c->overflow ); @@ -200,19 +282,24 @@ void processStream(Connection *c) // breakup the buffer using its changed length i = 0; - k = strlen(command); + k = strlen(command); // current length of the command buffer telling us where to start copy in l = strlen((char *)buffer); - // DIAG(F("\nCommand buffer: [%s]:[%d:%d:%d]\n"), command, i, l, k ); + DIAG(F("\nCommand buffer: [%s]:[%d:%d:%d]\n"), command, i, l, k ); while (i < l) { + // DIAG(F("\nl: %d k: %d , i: %d"), l, k, i); command[k] = buffer[i]; if (buffer[i] == c->delimiter) - { - command[k+1] = '\0'; - DIAG(F("Command: [%d:%e]\n"),_rseq[c->id], command); + { // closing bracket need to fix if there is none before an opening bracket ? - parse(c, (byte *)command, true); - // sendReply(c->client, command, c); + command[k+1] = '\0'; + + DIAG(F("Command: [%d:%e]\n"),_rseq[c->id], command); +#ifdef DCCEX_ENABLED + sendToDCC(c, command, true); +#else + sendReply(c, command); +#endif _rseq[c->id]++; j = 0; k = 0; @@ -248,7 +335,7 @@ void withrottleProcessor(Connection *c) /** * @brief Reads what is available on the incomming TCP stream and hands it over to the protocol handler. * - * @param c Pointer to the connection contining relevant information handling the data from that connection + * @param c Pointer to the connection struct contining relevant information handling the data from that connection */ void TransportProcessor::readStream(Connection *c) @@ -293,10 +380,12 @@ void TransportProcessor::readStream(Connection *c) } } - IPAddress remote = c->client->remoteIP(); + // IPAddress remote = c->client->remoteIP(); // only available in my modified Client.h file buffer[count] = '\0'; // terminate the string properly - DIAG(F("\nReceived packet of size:[%d] from [%d.%d.%d.%d]\n"), count, remote[0], remote[1], remote[2], remote[3]); + + // DIAG(F("\nReceived packet of size:[%d] from [%d.%d.%d.%d]\n"), count, remote[0], remote[1], remote[2], remote[3]); + DIAG(F("\nReceived packet of size:[%d]\n"), count); DIAG(F("Client #: [%d]\n"), c->id); DIAG(F("Packet: [%e]\n"), buffer); @@ -307,49 +396,75 @@ void TransportProcessor::readStream(Connection *c) /** * @brief Sending a reply by using the StringFormatter (this will result in every byte send individually which may/will create an important Network overhead). * Here we hook back into the DCC code for actually processing the command using a DCCParser. Alternatively we could use MemeStream in order to build the entiere reply - * before ending it. + * before ending it (cf. Scratch pad below) * * @param stream Actually the Client to whom to send the reply. As Clients implement Print this is working * @param command The reply to be send ( echo as in sendReply() ) * @param blocking if set to true will instruct the DCC code to not use the async callback functions */ -void parse(Connection* c , byte *command, bool blocking) +void parse(Print *stream, byte *command, bool blocking) { DIAG(F("DCC parsing: [%e]\n"), command); // echo back (as mock parser ) - StringFormatter::send(c->client, F("reply to: %s"), command); + StringFormatter::send(stream, F("reply to: %s"), command); } -/** - * @brief Sending a reply without going through the StringFormatter. Sends the repy in one go; For testing purposes; Adds sequence numbers to the reply - * - * @param client Client who send the command to which the reply shall be send - * @param command Command initaliy recieved to be echoed back - */ -void sendReply(Client *client, char *command, uint8_t c) -{ - char *number; - char seqNumber[6]; - int i = 0; - - memset(reply, 0, MAX_ETH_BUFFER); // reset reply - number = strrchr(command, ':'); // replace the int after the last ':' - while( &command[i] != number ) { // copy command into the reply upto the last ':' - reply[i] = command[i]; - i++; - } - - strcat((char *)reply, ":"); - itoa(_sseq[c], seqNumber, 10); - strcat((char *)reply, seqNumber); - strcat((char *)reply, ">"); - DIAG(F("Response: [%e]"), (char *)reply); - if (client->connected()) - { - client->write(reply, strlen((char *)reply)); - _sseq[c]++; - DIAG(F(" send\n")); - } -}; \ No newline at end of file +/* + // Alternative reply mechanism using MemStream thus allowing to send all in one go using the parser + streamer.setBufferContentPosition(0, 0); + + // Parse via MemBuffer to be replaced by DCCEXparser.parse later + + 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 *)reply); + if (clients[i]->connected()) + { + clients[i]->write(reply, streamer.available()); + } + } +*/ +/* This should work but creates a segmentation fault ?? + + // check if we have one parameter with name 'jmri' then send the payload directly and don't call the callback + preq = httpReq.getParsedRequest(); + DIAG(F("Check parameter count\n")); + if (*preq.paramCount == 1) + { + Params *p; + int cmp; + p = httpReq.getParam(1); + + DIAG(F("Parameter name[%s]\n"), p->name); + DIAG(F("Parameter value[%s]\n"), p->value); + + cmp = strcmp("jmri", p->name); + if ( cmp == 0 ) { + memset(buffer, 0, MAX_ETH_BUFFER); // reset PacktBuffer + strncpy((char *)buffer, p->value, strlen(p->value)); + jmriHandler(client, c); + } else { + DIAG(F("Callback 1\n")); + httpReq.callback(&preq, client); + } + } + else + { + DIAG(F("Callback 2\n")); + httpReq.callback(&preq, client); + } + DIAG(F("ResetRequest\n")); + httpReq.resetRequest(); + + } // else do nothing and wait for the next packet +} +*/ \ No newline at end of file diff --git a/TransportProcessor.h b/TransportProcessor.h index 958b482..b74920b 100644 --- a/TransportProcessor.h +++ b/TransportProcessor.h @@ -23,44 +23,47 @@ #include #include +#define DCCEX_ENABLED -#define MAX_ETH_BUFFER 64 // maximum length we read in one go from a TCP packet. Anything longer in one go send to the Arduino may result in unpredictable behaviour. - // idealy the windowsize should be set accordingly so that the sender knows to produce only max 250 size packets. -#define MAX_OVERFLOW MAX_ETH_BUFFER/2 // length of the overflow buffer to be used for a given connection. -#define MAX_JMRI_CMD 32 // MAX Length of a JMRI Command -typedef enum { - DCCEX, // if char[0] = < opening bracket the client should be a JMRI / DCC EX client_h - WITHROTTLE, // - HTTP, // If char[0] = G || P || D; if P then char [1] = U || O || A - N_DIAG, // '#' send form a telnet client as FIRST message will then reroute all DIAG messages to that client whilst being able to send jmri type commands +#define MAX_ETH_BUFFER 64 // maximum length we read in one go from a TCP packet. Anything longer in one go send to the Arduino may result in unpredictable behaviour. + // idealy the windowsize should be set accordingly so that the sender knows to produce only max 250 size packets. +#define MAX_OVERFLOW MAX_ETH_BUFFER / 2 // length of the overflow buffer to be used for a given connection. +#define MAX_JMRI_CMD 32 // MAX Length of a JMRI Command +typedef enum +{ + DCCEX, // if char[0] = < opening bracket the client should be a JMRI / DCC EX client_h + WITHROTTLE, // + HTTP, // If char[0] = G || P || D; if P then char [1] = U || O || A + N_DIAG, // '#' send form a telnet client as FIRST message will then reroute all DIAG messages to that client whilst being able to send jmri type commands UNKNOWN_PROTOCOL } appProtocol; struct Connection; -using appProtocolCallback = void(*)(Connection* c); +using appProtocolCallback = void (*)(Connection *c); -struct Connection { - uint8_t id; - Client* client; - char overflow[MAX_OVERFLOW]; - appProtocol p; - char delimiter = '\0'; - bool isProtocolDefined = false; +struct Connection +{ + uint8_t id; + Client *client; + char overflow[MAX_OVERFLOW]; + appProtocol p; + char delimiter = '\0'; + bool isProtocolDefined = false; appProtocolCallback appProtocolHandler; }; - - -class TransportProcessor +class TransportProcessor { +private: +#ifdef DCCEX_ENABLED + void sendToDCC(Connection *c, char *command, bool blocking); +#endif public: - - void readStream(Connection *c); // reads incomming packets and hands over to the commandHandle for taking the stream apart for commands + void readStream(Connection *c); // reads incomming packets and hands over to the commandHandle for taking the stream apart for commands TransportProcessor(){}; ~TransportProcessor(){}; - }; #endif // !Transport_h \ No newline at end of file