From c9c5f6a67b61e2481cdcacb32a495e4803615196 Mon Sep 17 00:00:00 2001 From: Gregor Baues Date: Thu, 22 Oct 2020 21:41:56 +0200 Subject: [PATCH] Setup ok; some fixes; DCCParser to be added; StringFormatter done --- CVReader.cpp | 73 ------------------------------ CommandStation-EX.ino | 31 ++++++++++++- NetworkInterface.cpp | 2 +- StringFormatter.cpp | 76 +++++++++++++++++++++++++++++-- StringFormatter.h | 5 +++ TransportProcessor.cpp | 100 ++++++++--------------------------------- 6 files changed, 127 insertions(+), 160 deletions(-) delete mode 100644 CVReader.cpp diff --git a/CVReader.cpp b/CVReader.cpp deleted file mode 100644 index e7bb6ed..0000000 --- a/CVReader.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * © 2020, Gregor Baues, Chris Harlow. All rights reserved. - * - * This is a basic, no frills CVreader example of a DCC++ compatible setup. - * There are more advanced examples in the examples folder i - */ -#include - -// #include "DCCEX.h" -#include "MemoryFree.h" -#include "DIAG.h" - -#include "NetworkInterface.h" - -// DCCEXParser serialParser; - -/** - * @brief User define callback for HTTP requests. The Network interface will provide for each http request a parsed request object - * and the client who send the request are provided. Its up to the user to use the req as he sees fits. Below is just a scaffold to - * demonstrate the workings. - * - * @param req Parsed request object - * @param client Originator of the request to reply to - */ - - -void httpRequestHandler(ParsedRequest *req, Client* client) { - DIAG(F("\nParsed Request:")); - DIAG(F("\nMethod: [%s]"), req->method); - DIAG(F("\nURI: [%s]"), req->uri); - DIAG(F("\nHTTP version: [%s]"), req->version); - DIAG(F("\nParameter count:[%d]\n"), *req->paramCount); - - // result = doSomething(); // obtain result to be send back; fully prepare the serialized HTTP response! - - // client->write(result); -} - - -void setup() -{ - - Serial.begin(115200); - while (!Serial) - { - ; // wait for serial port to connect. just in case - } - - // DCC::begin(STANDARD_MOTOR_SHIELD); - 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::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, MQTT, 8888); // sending over MQTT. - // NetworkInterface::setup(WIFI, UDP, 8888); // Setup without port will use the by default port 2560 :: DOES NOT WORK - // NetworkInterface::setup(WIFI); // setup without port and protocol will use by default TCP on port 2560 - // NetworkInterface::setup(); // all defaults ETHERNET, TCP on port 2560 - - DIAG(F("\nNetwork Setup done ...")); - - - DIAG(F("\nFree RAM after network init: [%d]\n"),freeMemory()); - DIAG(F("\nReady for DCC Commands ...")); -} - -void loop() -{ - // DCC::loop(); - NetworkInterface::loop(); - - // serialParser.loop(Serial); -} \ No newline at end of file diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 9749fea..1c9c226 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -17,6 +17,16 @@ // to be issued from the USB serial console. DCCEXParser serialParser; +// (1) Start NetworkInterface - HTTP callback +void httpRequestHandler(ParsedRequest *req, Client* client) { + DIAG(F("\nParsed Request:")); + DIAG(F("\nMethod: [%s]"), req->method); + DIAG(F("\nURI: [%s]"), req->uri); + DIAG(F("\nHTTP version: [%s]"), req->version); + DIAG(F("\nParameter count:[%d]\n"), *req->paramCount); +} +// (1) End NetworkInterface - HTTP callback + void setup() { // The main sketch has responsibilities during setup() @@ -49,6 +59,23 @@ void setup() // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 DCC::begin(MOTOR_SHIELD_TYPE); + + // (2) Start NetworkInterface - The original WifiInterface is still there but disabled + + 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::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 + // NetworkInterface::setup(WIFI); // setup without port and protocol will use by default TCP on port 2560 + // NetworkInterface::setup(); // all defaults ETHERNET, TCP on port 2560 + + DIAG(F("\nNetwork Setup done ...")); + DIAG(F("\nFree RAM after network init: [%d]\n"),freeMemory()); + + // (2) End starting NetworkInterface + LCD(1,F("Ready")); } @@ -67,7 +94,9 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif - +// (3) Start Loop NetworkInterface + NetworkInterface::loop(); +// (3) End Loop NetworkInterface LCDDisplay::loop(); // ignored if LCD not in use // Optionally report any decrease in memory (will automatically trigger on first call) diff --git a/NetworkInterface.cpp b/NetworkInterface.cpp index 5c1cd29..413ebce 100644 --- a/NetworkInterface.cpp +++ b/NetworkInterface.cpp @@ -24,7 +24,7 @@ #include "EthernetSetup.h" #include "WifiSetup.h" -HttpCallback NetworkInterface::httpCallback; +HttpCallback NetworkInterface::httpCallback = 0; Transport *NetworkInterface::wifiTransport; Transport *NetworkInterface::ethernetTransport; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index e99cfc3..261794a 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -31,6 +31,8 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" + bool Diag::ACK=false; bool Diag::CMD=false; bool Diag::WIFI=false; @@ -44,6 +46,14 @@ void StringFormatter::diag( const __FlashStringHelper* input...) { send2(diagSerial,input,args); } +void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { + if (!LCDDisplay::lcdDisplay) return; + LCDDisplay::lcdDisplay->setRow(row); + va_list args; + va_start(args, input); + send2(LCDDisplay::lcdDisplay,input,args); +} + void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) { va_list args; va_start(args, input); @@ -56,7 +66,6 @@ void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) send2(&stream,input,args); } - void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) { // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output @@ -66,6 +75,13 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va char c=pgm_read_byte_near(flash+i); if (c=='\0') return; if(c!='%') { stream->print(c); continue; } + + bool formatContinues=false; + byte formatWidth=0; + bool formatLeft=false; + do { + + formatContinues=false; i++; c=pgm_read_byte_near(flash+i); switch(c) { @@ -73,14 +89,34 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va case 'c': stream->print((char) va_arg(args, int)); break; case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; + case 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break; case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break; - case 'd': stream->print(va_arg(args, int), DEC); break; - case 'l': stream->print(va_arg(args, long), DEC); break; + case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; + case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print(va_arg(args, int), HEX); break; case 'f': stream->print(va_arg(args, double), 2); break; + //format width prefix + case '-': + formatLeft=true; + formatContinues=true; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + formatWidth=formatWidth * 10 + (c-'0'); + formatContinues=true; + break; } + } while(formatContinues); } va_end(args); } @@ -94,6 +130,17 @@ void StringFormatter::printEscapes(Print * stream,char * input) { } } +void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) { + + if (!stream) return; + char* flash=(char*)input; + for(int i=0; ; ++i) { + char c=pgm_read_byte_near(flash+i); + printEscape(stream,c); + if (c=='\0') return; + } +} + void StringFormatter::printEscape( char c) { printEscape(diagSerial,c); } @@ -109,4 +156,27 @@ void StringFormatter::printEscape(Print * stream, char c) { default: stream->print(c); } } + + +void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) { + if (width==0) { + stream->print(value, DEC); + return; + } + + int digits=(value <= 0)? 1: 0; // zero and negative need extra digot + long v=value; + while (v) { + v /= 10; + digits++; + } + + if (formatLeft) stream->print(value, DEC); + while(digitsprint(' '); + digits++; + } + if (!formatLeft) stream->print(value, DEC); + } + diff --git a/StringFormatter.h b/StringFormatter.h index 991808f..1661e36 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -28,6 +28,7 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" class Diag { public: static bool ACK; @@ -43,15 +44,18 @@ class StringFormatter static void send(Print & serial, const __FlashStringHelper* input...); static void printEscapes(Print * serial,char * input); + static void printEscapes(Print * serial,const __FlashStringHelper* input); static void printEscape(Print * serial, char c); // DIAG support static Print * diagSerial; static void diag( const __FlashStringHelper* input...); + static void lcd(byte row, const __FlashStringHelper* input...); static void printEscapes(char * input); static void printEscape( char c); + static void setDiagOut(Connection *c) { if ( c->client->connected() ) { diagSerial = c->client; @@ -63,6 +67,7 @@ class StringFormatter private: static void send2(Print * serial, const __FlashStringHelper* input,va_list args); + static void printPadded(Print* stream, long value, byte width, bool formatLeft); }; #endif diff --git a/TransportProcessor.cpp b/TransportProcessor.cpp index 75dc82c..13cf64f 100644 --- a/TransportProcessor.cpp +++ b/TransportProcessor.cpp @@ -48,6 +48,11 @@ 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 + * ev. send a 401 error back + */ uint8_t i, l = 0; ParsedRequest preq; l = strlen((char *)buffer); @@ -158,8 +163,6 @@ 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; @@ -178,22 +181,18 @@ 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 ); @@ -201,24 +200,19 @@ void processStream(Connection *c) // breakup the buffer using its changed length i = 0; - k = strlen(command); // current length of the command buffer telling us where to start copy in + k = strlen(command); l = strlen((char *)buffer); // 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) - { // closing bracket need to fix if there is none before an opening bracket ? - + { command[k+1] = '\0'; - DIAG(F("Command: [%d:%e]\n"),_rseq[c->id], command); - // parse(client, buffer, true); + parse(c, (byte *)command, true); // sendReply(c->client, command, c); - // memset(command, 0, MAX_JMRI_CMD); // clear out the command - _rseq[c->id]++; j = 0; k = 0; @@ -254,7 +248,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 struct contining relevant information handling the data from that connection + * @param c Pointer to the connection contining relevant information handling the data from that connection */ void TransportProcessor::readStream(Connection *c) @@ -313,21 +307,21 @@ 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 (cf. Scratch pad below) + * 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 parse(Print *stream, byte *command, bool blocking) +void parse(Connection* c , byte *command, bool blocking) { DIAG(F("DCC parsing: [%e]\n"), command); // echo back (as mock parser ) - StringFormatter::send(stream, F("reply to: %s"), command); + StringFormatter::send(c->client, F("reply to: %s"), command); } /** - * @brief Sending a reply without going through the StringFormatter. Sends the repy in one go + * @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 @@ -358,62 +352,4 @@ void sendReply(Client *client, char *command, uint8_t c) _sseq[c]++; DIAG(F(" send\n")); } -}; - -/* - // 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 +}; \ No newline at end of file