From 740ba6859ab4bae9fab29e45bd2eb6f444df70c6 Mon Sep 17 00:00:00 2001 From: Gregor Baues Date: Thu, 22 Oct 2020 19:25:20 +0200 Subject: [PATCH] Inital integration - StringFormatter to be fixed --- CVReader.cpp | 73 +++++++ DCCEX.h | 2 +- DIAG copy.h | 23 +++ EthernetInterface.cpp | 304 ----------------------------- EthernetInterface.h | 107 ---------- EthernetSetup.cpp | 113 +++++++++++ EthernetSetup.h | 41 ++++ HttpRequest.cpp | 432 +++++++++++++++++++++++++++++++++++++++++ HttpRequest.h | 123 ++++++++++++ MemoryFree.cpp | 52 +++++ MemoryFree.h | 14 ++ NetworkInterface.cpp | 137 +++++++++++++ NetworkInterface.h | 63 ++++++ NetworkSetup.cpp | 23 +++ NetworkSetup.h | 61 ++++++ StringFormatter.cpp | 76 +------- StringFormatter.h | 15 +- Transport.cpp | 173 +++++++++++++++++ Transport.h | 83 ++++++++ TransportProcessor.cpp | 419 +++++++++++++++++++++++++++++++++++++++ TransportProcessor.h | 66 +++++++ WifiSetup.cpp | 110 +++++++++++ WifiSetup.h | 59 ++++++ platformio.ini | 6 +- 24 files changed, 2085 insertions(+), 490 deletions(-) create mode 100644 CVReader.cpp create mode 100644 DIAG copy.h delete mode 100644 EthernetInterface.cpp delete mode 100644 EthernetInterface.h create mode 100644 EthernetSetup.cpp create mode 100644 EthernetSetup.h create mode 100644 HttpRequest.cpp create mode 100644 HttpRequest.h create mode 100644 MemoryFree.cpp create mode 100644 MemoryFree.h create mode 100644 NetworkInterface.cpp create mode 100644 NetworkInterface.h create mode 100644 NetworkSetup.cpp create mode 100644 NetworkSetup.h create mode 100644 Transport.cpp create mode 100644 Transport.h create mode 100644 TransportProcessor.cpp create mode 100644 TransportProcessor.h create mode 100644 WifiSetup.cpp create mode 100644 WifiSetup.h diff --git a/CVReader.cpp b/CVReader.cpp new file mode 100644 index 0000000..e7bb6ed --- /dev/null +++ b/CVReader.cpp @@ -0,0 +1,73 @@ +/* + * © 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/DCCEX.h b/DCCEX.h index fb4baef..fe78558 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -10,7 +10,7 @@ #include "DCCEXParser.h" #include "version.h" #include "WifiInterface.h" -#include "EthernetInterface.h" +#include "NetworkInterface.h" #include "LCD_Implementation.h" #include "freeMemory.h" #include diff --git a/DIAG copy.h b/DIAG copy.h new file mode 100644 index 0000000..e0d4383 --- /dev/null +++ b/DIAG copy.h @@ -0,0 +1,23 @@ +/* + * © 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/EthernetInterface.cpp b/EthernetInterface.cpp deleted file mode 100644 index 3498871..0000000 --- a/EthernetInterface.cpp +++ /dev/null @@ -1,304 +0,0 @@ -/* - * © 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 . - * - * Ethernet Interface added by Gregor Baues - */ - -#include "EthernetInterface.h" -#include "DIAG.h" -#include "StringFormatter.h" - -//#include -#include -#include - - -// 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(); -} diff --git a/EthernetInterface.h b/EthernetInterface.h deleted file mode 100644 index b702f04..0000000 --- a/EthernetInterface.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * © 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 . - * - * Ethernet Interface added by Gregor Baues - */ - -#ifndef EthernetInterface_h -#define EthernetInterface_h - -#include "DCCEXParser.h" -#include "MemStream.h" -#include -#include -#include - -/* 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 diff --git a/EthernetSetup.cpp b/EthernetSetup.cpp new file mode 100644 index 0000000..0955dbe --- /dev/null +++ b/EthernetSetup.cpp @@ -0,0 +1,113 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 +#include "DIAG.h" +#include "EthernetSetup.h" + +EthernetServer* EthernetSetup::setup() +{ + + DIAG(F("\nInitialize Ethernet with DHCP")); + if (Ethernet.begin(mac) == 0) + { + DIAG(F("\nFailed to configure Ethernet using DHCP ... Trying with fixed IP")); + Ethernet.begin(mac, IPAddress(IP_ADDRESS)); // default ip address + + if (Ethernet.hardwareStatus() == EthernetNoHardware) + { + DIAG(F("\nEthernet shield was not found. Sorry, can't run without hardware. :(")); + return 0; + }; + if (Ethernet.linkStatus() == LinkOFF) + { + DIAG(F("\nEthernet cable is not connected.")); + return 0; + } + } + + maxConnections = MAX_SOCK_NUM; + + if (Ethernet.hardwareStatus() == EthernetW5100) + { + DIAG(F("\nW5100 Ethernet controller detected.")); + maxConnections = 4; // Max supported officaly by the W5100 but i have been running over 8 as well. Perf has to be evaluated though comparing 4 vs. 8 connections + } + else if (Ethernet.hardwareStatus() == EthernetW5200) + { + DIAG(F("\nW5200 Ethernet controller detected.")); + maxConnections = 8; + } + else if (Ethernet.hardwareStatus() == EthernetW5500) + { + DIAG(F("W5500 Ethernet controller detected.")); + maxConnections = 8; + } + + DIAG(F("\nNetwork Protocol: [%s]"), protocol ? "UDP" : "TCP"); + switch (protocol) + { + case UDP: + { + if (udp.begin(port)) + { + maxConnections = 1; // there is only one UDP object listening for incomming data + connected = true; + } + else + { + DIAG(F("\nUDP client failed to start")); + connected = false; + } + break; + }; + case TCP: + { + server = new EthernetServer(port); + server->begin(); + connected = true; + break; + }; + case MQTT: + { + // do the MQTT setup stuff ... + }; + default: + { + DIAG(F("\nUnkown Ethernet protocol; Setup failed")); + connected = false; + break; + } + } + if (connected) + { + ip = Ethernet.localIP(); + 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] "), dnsip[0], dnsip[1], dnsip[2], dnsip[3]); + DIAG(F("\nNumber of connections: [%d]"), maxConnections); + if( protocol == UDP ) return 0; // no server here as we use UDB + return server; + } + return 0; +} + +EthernetSetup::EthernetSetup() {} +EthernetSetup::EthernetSetup(uint16_t p, protocolType pt ) { port = p; protocol = pt; } +EthernetSetup::~EthernetSetup() {} + diff --git a/EthernetSetup.h b/EthernetSetup.h new file mode 100644 index 0000000..45cbb2d --- /dev/null +++ b/EthernetSetup.h @@ -0,0 +1,41 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 EthernetSetup_h +#define EthernetSetup_h + +#include +#include "NetworkSetup.h" + +class EthernetSetup: public NetworkSetup { + +private: + + EthernetServer* server; + EthernetUDP udp; + +public: + + // EthernetServer *setup(uint16_t port); + EthernetServer *setup(); + + EthernetSetup(); + EthernetSetup(uint16_t port, protocolType protocol); + ~EthernetSetup(); +}; + +#endif \ No newline at end of file diff --git a/HttpRequest.cpp b/HttpRequest.cpp new file mode 100644 index 0000000..ab9815e --- /dev/null +++ b/HttpRequest.cpp @@ -0,0 +1,432 @@ +/* + * © 2012 Francisco G. Paletta, © 2020 Gregor Baues. All rights reserved. + * + * 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 "Arduino.h" +#include "HttpRequest.h" +#include "NetworkInterface.h" +#include "DIAG.h" + +// public interface to the parsed request +// static ParsedRequest req; + +HttpRequest::HttpRequest() +{ + resetRequest(); + req.method = method; + req.uri = uri; + req.version = version; + req.paramCount = ¶mCount; + /** + * @todo add list of parameters + * + */ +} + +ParsedRequest HttpRequest::getParsedRequest() +{ + return req; +} + +void HttpRequest::resetRequest() +{ + + freeParamMem(firstParam); + freeCookieMem(firstCookie); + + parseStatus = HTTP_PARSE_INIT; + method[0] = '\0'; + uri[0] = '\0'; + version[0] = '\0'; + firstParam = NULL; + firstCookie = NULL; + paramCount = 0; + cookieCount = 0; + tmpParamName[0] = '\0'; + tmpParamValue[0] = '\0'; + tmpAttribName[0] = '\0'; + tmpAttribValue[0] = '\0'; + tmpCookieName[0] = '\0'; + tmpCookieValue[0] = '\0'; + dataBlockLength = 0; + dataCount = 0; +} + +void HttpRequest::freeParamMem(Params *paramNode) +{ + if (paramNode != NULL) + { + freeParamMem(paramNode->next); + delete paramNode; + } +} + +void HttpRequest::freeCookieMem(Cookies *cookieNode) +{ + if (cookieNode != NULL) + { + freeCookieMem(cookieNode->next); + delete cookieNode; + } +} + +void HttpRequest::parseRequest(char c) +{ + + char cStr[2]; + cStr[0] = c; + cStr[1] = '\0'; + switch (parseStatus) + { + + case HTTP_METHOD: + if (c == ' ') + parseStatus = HTTP_URI; + else if (strlen(method) < HTTP_REQ_METHOD_LENGTH - 1) + strcat(method, cStr); + break; + + case HTTP_URI: + // DIAG(F("HTTP_URI: %c\n"), c); + if (c == ' ') + parseStatus = HTTP_VERSION; + else if (c == '?') + parseStatus = HTTP_GET_NAME; + else if (strlen(uri) < HTTP_REQ_URI_LENGTH - 1) + strcat(uri, cStr); + break; + + case HTTP_GET_NAME: + // DIAG(F("HTTP_GET_NAME: %c\n"), c); + if (c == ' ') + parseStatus = HTTP_VERSION; + else if (c != '=') + { + if (strlen(tmpParamName) < HTTP_REQ_PARAM_NAME_LENGTH - 1) + strcat(tmpParamName, cStr); + } + else + parseStatus = HTTP_GET_VALUE; + break; + + case HTTP_GET_VALUE: + // DIAG(F("HTTP_GET_VALUE: %c\n"), c); + if (c == '&') + { + addParam(); + parseStatus = HTTP_GET_NAME; + } + else if (c == ' ') + { + addParam(); + parseStatus = HTTP_VERSION; + } + else if (strlen(tmpParamValue) < HTTP_REQ_PARAM_VALUE_LENGTH - 1) + strcat(tmpParamValue, cStr); + break; + + case HTTP_VERSION: + // DIAG(F("HTTP_VERSION: %c\n"), c); + if (c == '\n') + parseStatus = HTTP_NEW_LINE; + else if (c != '\r' && strlen(version) < HTTP_REQ_VERSION_LENGTH - 1) + strcat(version, cStr); + break; + + case HTTP_NEW_LINE: + // DIAG(F("HTTP_NEW_LINE: %c\n"), c); + if (c != '\r' && c != '\n') + { + parseStatus = HTTP_ATTRIB_NAME; + tmpAttribName[0] = '\0'; + tmpAttribValue[0] = '\0'; + } + else + { + if (strcmp(method, "POST") == 0 && dataBlockLength > 0) + parseStatus = HTTP_POST_NAME; + else + // DIAG(F("HTTP_REQUEST_END: %c\n"), c); + parseStatus = HTTP_REQUEST_END; + break; + } + break; + + case HTTP_ATTRIB_NAME: + // DIAG(F("HTTP_ATTRIB_NAME: %c\n"), c); + if (c == '\n') + parseStatus = HTTP_NEW_LINE; + else if (c != ':' && c != ' ' && c != '\r') + { + if (strlen(tmpAttribName) < HTTP_REQ_ATTRIB_NAME_LENGTH - 1) + strcat(tmpAttribName, cStr); + } + else if (c == ' ') + { + if (strcmp(tmpAttribName, "Cookie") == 0) + parseStatus = HTTP_COOKIE_NAME; + else + parseStatus = HTTP_ATTRIB_VALUE; + } + break; + + case HTTP_ATTRIB_VALUE: + // DIAG(F("HTTP_ATTRIB_VALUE: %c\n"), c); + if (c == '\n') + { + addAttrib(); + parseStatus = HTTP_NEW_LINE; + } + else if (c != '\r') + if (strlen(tmpAttribValue) < HTTP_REQ_ATTRIB_VALUE_LENGTH - 1) + strcat(tmpAttribValue, cStr); + break; + + case HTTP_POST_NAME: + dataCount++; + if (c != '=') + { + if (strlen(tmpParamName) < HTTP_REQ_PARAM_NAME_LENGTH - 1) + strcat(tmpParamName, cStr); + } + else + parseStatus = HTTP_POST_VALUE; + if (dataCount > dataBlockLength) + { + addParam(); + // DIAG(F("HTTP_REQUEST_END: %c\n"), c); + parseStatus = HTTP_REQUEST_END; + } + break; + + case HTTP_POST_VALUE: + dataCount++; + if (c == '&') + { + addParam(); + parseStatus = HTTP_POST_NAME; + } + else if (strlen(tmpParamValue) < HTTP_REQ_PARAM_VALUE_LENGTH - 1) + strcat(tmpParamValue, cStr); + if (dataCount > dataBlockLength) + { + addParam(); + // DIAG(F("HTTP_REQUEST_END: %c\n"), c); + parseStatus = HTTP_REQUEST_END; + } + break; + + case HTTP_COOKIE_NAME: + if (c == '\n') + parseStatus = HTTP_NEW_LINE; + else if (c != '=') + { + if (c != ' ' && strlen(tmpCookieName) < HTTP_REQ_COOKIE_NAME_LENGTH - 1) + strcat(tmpCookieName, cStr); + } + else + parseStatus = HTTP_COOKIE_VALUE; + break; + + case HTTP_COOKIE_VALUE: + if (c == ';') + { + addCookie(); + parseStatus = HTTP_COOKIE_NAME; + } + else if (c == '\n') + { + addCookie(); + parseStatus = HTTP_NEW_LINE; + } + else if (c != '\r' && c != ' ' && strlen(tmpCookieValue) < HTTP_REQ_COOKIE_VALUE_LENGTH - 1) + strcat(tmpCookieValue, cStr); + break; + } +} + +bool HttpRequest::endOfRequest() +{ + if (parseStatus == HTTP_REQUEST_END) + return true; + else + return false; +} + +void HttpRequest::addParam() +{ + + Params **cursor; + + cursor = &firstParam; + while ((*cursor) != NULL) + { + if (strcmp((*cursor)->name, tmpParamName) == 0) + break; + cursor = &((*cursor)->next); + } + if ((*cursor) == NULL) + { + // DIAG(F("New Param: %s\n"), tmpParamName); + (*cursor) = new Params; + strcpy((*cursor)->name, tmpParamName); + strcpy((*cursor)->value, tmpParamValue); + (*cursor)->next = NULL; + paramCount++; + } + + tmpParamName[0] = '\0'; + tmpParamValue[0] = '\0'; +} + +void HttpRequest::addCookie() +{ + + Cookies **cursor; + + cursor = &firstCookie; + while ((*cursor) != NULL) + { + if (strcmp((*cursor)->name, tmpCookieName) == 0) + break; + cursor = &((*cursor)->next); + } + if ((*cursor) == NULL) + { + (*cursor) = new Cookies; + strcpy((*cursor)->name, tmpCookieName); + strcpy((*cursor)->value, tmpCookieValue); + (*cursor)->next = NULL; + cookieCount++; + } + + tmpCookieName[0] = '\0'; + tmpCookieValue[0] = '\0'; +} + +void HttpRequest::addAttrib() +{ + + if (strcmp(tmpAttribName, "Content-Length") == 0) + dataBlockLength = atoi(tmpAttribValue); + + tmpAttribName[0] = '\0'; + tmpAttribValue[0] = '\0'; +} + +uint8_t HttpRequest::getParam(uint8_t paramNum, char *name, char *value) +{ + uint8_t i = 0; + + Params **cursor; + + cursor = &firstParam; + while ((*cursor) != NULL) + { + i++; + if (i == paramNum) + { + strcpy(name, (*cursor)->name); + strcpy(value, (*cursor)->value); + break; + } + cursor = &((*cursor)->next); + } + + return i; +} + +Params* HttpRequest::getParam(uint8_t paramNum) +{ + uint8_t i = 0; + + Params **cursor; + + cursor = &firstParam; + while ((*cursor) != NULL) + { + i++; + if (i == paramNum) + { + break; + } + cursor = &((*cursor)->next); + } + return *cursor; +} + +uint8_t HttpRequest::getParam(char *name, char *value) +{ + + uint8_t pos = 0, i = 0; + Params **cursor; + + cursor = &firstParam; + while ((*cursor) != NULL) + { + i++; + if (strcmp((*cursor)->name, name) == 0) + { + strcpy(value, (*cursor)->value); + pos = i; + break; + } + cursor = &((*cursor)->next); + } + return pos; +} + +uint8_t HttpRequest::getCookie(uint8_t cookieNum, char *name, char *value) +{ + uint8_t i = 0; + + Cookies **cursor; + + cursor = &firstCookie; + while ((*cursor) != NULL) + { + i++; + if (i == cookieNum) + { + strcpy(name, (*cursor)->name); + strcpy(value, (*cursor)->value); + break; + } + cursor = &((*cursor)->next); + } + return i; +} + +uint8_t HttpRequest::getCookie(char *name, char *value) +{ + + uint8_t pos = 0, i = 0; + Cookies **cursor; + + cursor = &firstCookie; + while ((*cursor) != NULL) + { + i++; + if (strcmp((*cursor)->name, name) == 0) + { + strcpy(value, (*cursor)->value); + pos = i; + break; + } + cursor = &((*cursor)->next); + } + return pos; +} diff --git a/HttpRequest.h b/HttpRequest.h new file mode 100644 index 0000000..a57c7c7 --- /dev/null +++ b/HttpRequest.h @@ -0,0 +1,123 @@ + +/* + * © 2012 Francisco G. Paletta, © 2020 Gregor Baues. All rights reserved. + * + * 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 HttpRequest_h +#define HttpRequest_h + +#include "Ethernet.h" + +// Buffer lengths +#define HTTP_REQ_METHOD_LENGTH 10 //10 is enough +#define HTTP_REQ_URI_LENGTH 32 //adjust if you have long path/file names +#define HTTP_REQ_VERSION_LENGTH 10 //10 is enough +#define HTTP_REQ_PARAM_NAME_LENGTH 16 //adjust to meet your needs +#define HTTP_REQ_PARAM_VALUE_LENGTH 16 //adjust to meet your needs +#define HTTP_REQ_ATTRIB_NAME_LENGTH 16 //enough to track attribute name +#define HTTP_REQ_ATTRIB_VALUE_LENGTH 16 //enough to track "Content-Length" value +#define HTTP_REQ_COOKIE_NAME_LENGTH 10 //adjust to meet your needs +#define HTTP_REQ_COOKIE_VALUE_LENGTH 16 //adjust to meet your needs + +// Parsing status +#define HTTP_PARSE_INIT 0 //Initial Parser Status +#define HTTP_METHOD 0 //Parse the Method: GET POST UPDATE etc +#define HTTP_URI 1 //Parse the URI +#define HTTP_GET_NAME 11 //Parse the GET parameter NAME +#define HTTP_GET_VALUE 12 //Parse the GET parameter VALUE +#define HTTP_VERSION 2 //Parse the version: HTTP1.1 +#define HTTP_NEW_LINE 3 //Starts reading a new line +#define HTTP_ATTRIB_NAME 41 //Read the attibutes NAME +#define HTTP_ATTRIB_VALUE 42 //Read the attribute VALUE +#define HTTP_POST_NAME 51 //Read the POST parameter NAME +#define HTTP_POST_VALUE 52 //Read the POST paramenter VALUE +#define HTTP_COOKIE_NAME 61 //Read the COOKIE NAME +#define HTTP_COOKIE_VALUE 62 //Read the COOKIE VALUE +#define HTTP_REQUEST_END 99 //Finished reading the HTTP Request + +// returned to the callback +struct ParsedRequest { + char* method; + char* uri; + char* version; + uint8_t* paramCount; + // uint8_t (*getByIndex)(int, char*, char*); + // uint8_t (*getByName)(char*, char*); +}; + + struct Params + { + char name[HTTP_REQ_PARAM_NAME_LENGTH]; + char value[HTTP_REQ_PARAM_VALUE_LENGTH]; + Params *next; + }; + +class HttpRequest +{ +private: + + // no Cookies + struct Cookies + { + char name[HTTP_REQ_COOKIE_NAME_LENGTH]; + char value[HTTP_REQ_COOKIE_VALUE_LENGTH]; + Cookies *next; + }; + + uint8_t parseStatus; + Params *firstParam; + Cookies *firstCookie; + + char tmpParamName[HTTP_REQ_PARAM_NAME_LENGTH]; + char tmpParamValue[HTTP_REQ_PARAM_VALUE_LENGTH]; + char tmpAttribName[HTTP_REQ_ATTRIB_NAME_LENGTH]; + char tmpAttribValue[HTTP_REQ_ATTRIB_NAME_LENGTH]; + char tmpCookieName[HTTP_REQ_COOKIE_NAME_LENGTH]; // no use + char tmpCookieValue[HTTP_REQ_COOKIE_VALUE_LENGTH]; // no use + + uint16_t dataBlockLength, dataCount; + + void addParam(); + void addAttrib(); + void addCookie(); // no use + void freeParamMem(Params *paramNode); + void freeCookieMem(Cookies *cookieNode); + + char method[HTTP_REQ_METHOD_LENGTH]; // user + char uri[HTTP_REQ_URI_LENGTH]; // user + char version[HTTP_REQ_VERSION_LENGTH]; // user + + uint8_t paramCount; // user + uint8_t cookieCount; // no use - no cookie support + + uint8_t getParam(uint8_t paramNum, char *name, char *value); // user + uint8_t getParam(char *name, char *value); // user + uint8_t getCookie(uint8_t cookieNum, char *name, char *value); // no use + uint8_t getCookie(char *name, char *value); // no use + + ParsedRequest req; + +public: + HttpRequest(); + void resetRequest(); + void parseRequest(char c); + bool endOfRequest(); + ParsedRequest getParsedRequest(); + Params* getParam(uint8_t paramNum); + void (* callback)(ParsedRequest *req, Client *client); +}; + +#endif diff --git a/MemoryFree.cpp b/MemoryFree.cpp new file mode 100644 index 0000000..b1e3801 --- /dev/null +++ b/MemoryFree.cpp @@ -0,0 +1,52 @@ +#if (ARDUINO >= 100) +#include +#else +#include +#endif + +extern unsigned int __heap_start; +extern void *__brkval; + +/* + * The free list structure as maintained by the + * avr-libc memory allocation routines. + */ +struct __freelist +{ + size_t sz; + struct __freelist *nx; +}; + +/* The head of the free list structure */ +extern struct __freelist *__flp; + +#include "MemoryFree.h" + +/* Calculates the size of the free list */ +int freeListSize() +{ + struct __freelist* current; + int total = 0; + for (current = __flp; current; current = current->nx) + { + total += 2; /* Add two bytes for the memory block's header */ + total += (int) current->sz; + } + + return total; +} + +int freeMemory() +{ + int free_memory; + if ((int)__brkval == 0) + { + free_memory = ((int)&free_memory) - ((int)&__heap_start); + } + else + { + free_memory = ((int)&free_memory) - ((int)__brkval); + free_memory += freeListSize(); + } + return free_memory; +} \ No newline at end of file diff --git a/MemoryFree.h b/MemoryFree.h new file mode 100644 index 0000000..d879b78 --- /dev/null +++ b/MemoryFree.h @@ -0,0 +1,14 @@ +#ifndef MEMORY_FREE_H +#define MEMORY_FREE_H + +#ifdef __cplusplus +extern "C" { +#endif + +int freeMemory(); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/NetworkInterface.cpp b/NetworkInterface.cpp new file mode 100644 index 0000000..5c1cd29 --- /dev/null +++ b/NetworkInterface.cpp @@ -0,0 +1,137 @@ +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 + +#include "DIAG.h" +#include "NetworkInterface.h" +// #include "Singelton.h" +// #include "WifiTransport.h" +#include "EthernetSetup.h" +#include "WifiSetup.h" + +HttpCallback NetworkInterface::httpCallback; + +Transport *NetworkInterface::wifiTransport; +Transport *NetworkInterface::ethernetTransport; +transportType t; + +void NetworkInterface::setup(transportType transport, protocolType protocol, uint16_t port) +{ + + bool ok = false; + + DIAG(F("\n[%s] Transport Setup In Progress ...\n"), transport ? "Ethernet" : "Wifi"); + + // configure the Transport and get Ethernet/Wifi server up and running + + t = transport; + switch (transport) + { + case WIFI: + { + WifiSetup wSetup(port, protocol); + + wifiTransport = new Transport(); + ok = wSetup.setup(); + if (ok) + { + wifiTransport->server = wSetup.getServer(); + wifiTransport->port = port; + wifiTransport->protocol = protocol; + wifiTransport->transport = transport; + wifiTransport->maxConnections = wSetup.maxConnections; + ok = wifiTransport->setup(); + } + break; + }; + case ETHERNET: + { + EthernetSetup eSetup(port, protocol); + + ethernetTransport = new Transport(); + ethernetTransport->server = eSetup.setup(); // returns (NULL) 0 if we run over UDP + ethernetTransport->port = port; + ethernetTransport->protocol = protocol; + ethernetTransport->transport = transport; + ethernetTransport->maxConnections = eSetup.maxConnections; // that has been determined during the ethernet/wifi setup + ok = ethernetTransport->setup(); // start the transport i.e. setup all the client connections; We don't need the setup object anymore from here on + break; + }; + default: + { + DIAG(F("\nERROR: Unknown Transport")); // Something went wrong + break; + } + } + DIAG(F("\n\n[%s] Transport %s ..."), transport ? "Ethernet" : "Wifi", ok ? "OK" : "Failed"); +} + +void NetworkInterface::setup(transportType tt, protocolType pt) +{ + NetworkInterface::setup(tt, pt, LISTEN_PORT); +} + +void NetworkInterface::setup(transportType tt) +{ + NetworkInterface::setup(tt, TCP, LISTEN_PORT); +} + +void NetworkInterface::setup() +{ + NetworkInterface::setup(ETHERNET, TCP, LISTEN_PORT); +} + +void NetworkInterface::loop() +{ + switch (t) + { + case WIFI: + { + if (wifiTransport->isConnected()){ + wifiTransport->loop(); + } + break; + } + case ETHERNET: + { + if (ethernetTransport->isConnected()) { + ethernetTransport->loop(); + } + break; + } + } +} + +void NetworkInterface::setHttpCallback(HttpCallback callback) +{ + httpCallback = callback; +} +HttpCallback NetworkInterface::getHttpCallback() +{ + return httpCallback; +} + +NetworkInterface::NetworkInterface() +{ + // DIAG(F("NetworkInterface created ")); +} + +NetworkInterface::~NetworkInterface() +{ + // DIAG(F("NetworkInterface destroyed")); +} \ No newline at end of file diff --git a/NetworkInterface.h b/NetworkInterface.h new file mode 100644 index 0000000..cdf34d8 --- /dev/null +++ b/NetworkInterface.h @@ -0,0 +1,63 @@ +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 NetworkInterface_h +#define NetworkInterface_h + +#include + +#include "Transport.h" +#include "HttpRequest.h" + +typedef enum protocolType { + TCP, + UDP, + MQTT +} protocolType; + +typedef enum transportType { + WIFI, // using an AT (Version >= V1.7) command enabled ESP8266 not to be used in conjunction with the WifiInterface though! not tested for conflicts + ETHERNET // using the EthernetShield +} transoprtType; + +// typedef void (*HttpCallback)(ParsedRequest *req, Client *client); +using HttpCallback = void(*)(ParsedRequest *req, Client *client); + +class NetworkInterface +{ +private: + + static Transport* wifiTransport; + static Transport* ethernetTransport; + static HttpCallback httpCallback; + +public: + + static void setHttpCallback(HttpCallback callback); + static HttpCallback getHttpCallback(); + static void setup(transportType t, protocolType p, uint16_t port); // specific port nummber + static void setup(transportType t, protocolType p); // uses default port number + static void setup(transportType t); // defaults for protocol/port + + static void setup(); // defaults for all as above plus CABLE (i.e. using EthernetShield ) as default + static void loop(); + + NetworkInterface(); + ~NetworkInterface(); +}; + +#endif \ No newline at end of file diff --git a/NetworkSetup.cpp b/NetworkSetup.cpp new file mode 100644 index 0000000..4c2da0f --- /dev/null +++ b/NetworkSetup.cpp @@ -0,0 +1,23 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 +#include "DIAG.h" +#include "NetworkSetup.h" + +NetworkSetup::NetworkSetup() {} +NetworkSetup::~NetworkSetup() {} \ No newline at end of file diff --git a/NetworkSetup.h b/NetworkSetup.h new file mode 100644 index 0000000..00aa601 --- /dev/null +++ b/NetworkSetup.h @@ -0,0 +1,61 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 NetworkSetup_h +#define NetworkSetup_h + +#include "Ethernet.h" +#include "NetworkInterface.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; + +class NetworkSetup +{ +private: +public: + IPAddress dnsip; + IPAddress ip; + uint8_t mac[6] = MAC_ADDRESS; + uint8_t maxConnections; + bool connected; // semantics is that the server has successfullt started or not; client connections will be started in the Transport object + protocolType protocol; + uint16_t port = LISTEN_PORT; // Default port + + NetworkSetup(); + ~NetworkSetup(); +}; + +#endif \ No newline at end of file diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 261794a..e99cfc3 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -31,8 +31,6 @@ #define __FlashStringHelper char #endif -#include "LCDDisplay.h" - bool Diag::ACK=false; bool Diag::CMD=false; bool Diag::WIFI=false; @@ -46,14 +44,6 @@ 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); @@ -66,6 +56,7 @@ 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 @@ -75,13 +66,6 @@ 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) { @@ -89,34 +73,14 @@ 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': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; - case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; + case 'd': stream->print(va_arg(args, int), DEC); break; + case 'l': stream->print(va_arg(args, long), DEC); 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); } @@ -130,17 +94,6 @@ 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); } @@ -156,27 +109,4 @@ 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 5cbccf1..991808f 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -19,6 +19,7 @@ #ifndef StringFormatter_h #define StringFormatter_h #include +#include "TransportProcessor.h" #if defined(ARDUINO_ARCH_SAMD) // Some processors use a gcc compiler that renames va_list!!! @@ -27,7 +28,6 @@ #define __FlashStringHelper char #endif -#include "LCDDisplay.h" class Diag { public: static bool ACK; @@ -43,19 +43,26 @@ 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; + } + } + static void resetDiagOut() { + diagSerial = &Serial; + } + 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/Transport.cpp b/Transport.cpp new file mode 100644 index 0000000..08017ea --- /dev/null +++ b/Transport.cpp @@ -0,0 +1,173 @@ +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 + +#include "DIAG.h" +#include "NetworkInterface.h" +#include "Transport.h" + +extern bool diagNetwork; +extern uint8_t diagNetworkClient; + +template +bool Transport::setup() { + if (protocol == TCP) { + connectionPool(server); // server should have started here so create the connection pool only for TCP though + } + t = new TransportProcessor(); + connected = true; // server & clients which will recieve/send data have all e setup and are available + return true; +} + +template +void Transport::loop() { + switch (protocol) + { + case UDP: + { + udpHandler(); + break; + }; + case TCP: + { + tcpSessionHandler(server); // for session oriented coms + break; + }; + case MQTT: + { + // MQTT + break; + }; + } +} + +template +void Transport::connectionPool(S *server) +{ + for (int i = 0; i < Transport::maxConnections; i++) + { + clients[i] = server->accept(); + connections[i].client = &clients[i]; + connections[i].id = i; + DIAG(F("\nConnection pool: [%d:%x]"), i, clients[i]); + } +} + +template +void Transport::udpHandler() +{ + // DIAG(F("UdpHandler\n")); + 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(buffer, MAX_ETH_BUFFER); /////////// Put into the TransportProcessor + // terminate buffer properly + // buffer[packetSize] = '\0'; + + // DIAG(F("Command: [%s]\n"),buffer); + // execute the command via the parser + // check if we have a response if yes then + // send the reply + // udp.beginPacket(udp.remoteIP(), udp.remotePort()); + // parse(&udp, (byte *)buffer, true); //////////// Put into the TransportProcessor + // udp.endPacket(); + + // clear out the PacketBuffer + // memset(buffer, 0, MAX_ETH_BUFFER); // reset PacktBuffer + return; + } +} + +/** + * @brief As tcpHandler but this time the connections are kept open (thus creating a statefull session) as long as the client doesn't disconnect. A connection + * pool has been setup beforehand and determines the number of available sessions depending on the network hardware. Commands crossing packet boundaries will be captured + * + */ +template +void Transport::tcpSessionHandler(S* server) +{ + // get client from the server + C client = server->accept(); + + // check for new client + if (client) + { + for (byte i = 0; i < Transport::maxConnections; 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; + DIAG(F("\nNew Client: [%d:%x]"), i, clients[i]); + break; + } + } + } + // check for incoming data from all possible clients + for (byte i = 0; i < Transport::maxConnections; i++) + { + if (clients[i] && clients[i].available() > 0) + { + // readStream(i); + t->readStream(&connections[i]); + } + // stop any clients which disconnect + for (byte i = 0; i < Transport::maxConnections; i++) + { + if (clients[i] && !clients[i].connected()) + { + DIAG(F("\nDisconnect client #%d"), i); + clients[i].stop(); + connections[i].isProtocolDefined = false; + if (diagNetworkClient == i && diagNetwork) { + diagNetwork = false; + StringFormatter::resetDiagOut(); + } + } + } + } +} + +template +Transport::Transport() +{ + // DIAG(F("Transport created ")); +} + +template +Transport::~Transport() +{ + // DIAG(F("Transport destroyed")); +} + +// explicitly instatiate to get the relevant copies for ethernet / wifi build @compile time +template class Transport; +template class Transport; + +/* + * Scratch pad Section + */ + diff --git a/Transport.h b/Transport.h new file mode 100644 index 0000000..1452302 --- /dev/null +++ b/Transport.h @@ -0,0 +1,83 @@ + +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 Transport_h +#define Transport_h + +#include +#include +#include + +#include +#include "TransportProcessor.h" + + +#define MAX_SOCK_NUM 8 // Maximum number of sockets allowed for any WizNet based EthernetShield. The W5100 only supports 4 +#define MAX_WIFI_SOCK 5 // ESP8266 doesn't support more than 5 connections in // +#define LISTEN_PORT 2560 // default listen port for the server + + +#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; + +// Emulate Serial1 on pins 6/7 if not present +#if defined(ARDUINO_ARCH_AVR) && !defined(HAVE_HWSERIAL1) +#include +SoftwareSerial Serial1(6, 7); // RX, TX +#define AT_BAUD_RATE 9600 +#else +#define AT_BAUD_RATE 115200 +#endif + +template class Transport +{ + +private: + C clients[MAX_SOCK_NUM]; // Client objects created by the connectionPool + Connection connections[MAX_SOCK_NUM]; // All the connections build by the connectionPool + bool connected = false; + TransportProcessor* t; // pointer to the object which handles the incomming flow + + void udpHandler(); // Reads from a Udp socket - todo add incomming queue for processing when the flow is faster than we can process commands + void tcpSessionHandler(S* server); // tcpSessionHandler -> connections are maintained open until close by the client + void connectionPool(S* server); // allocates the Sockets at setup time and creates the Connections + +public: + uint16_t port; + uint8_t protocol; // TCP or UDP + uint8_t transport; // WIFI or ETHERNET + S* server; // WiFiServer or EthernetServer + U* udp; // UDP socket object + uint8_t maxConnections; // number of supported connections depending on the network equipment used + + bool setup(); + void loop(); + + bool isConnected() { + return connected; + } + + Transport(); + ~Transport(); + +}; + +#endif // !Transport_h \ No newline at end of file diff --git a/TransportProcessor.cpp b/TransportProcessor.cpp new file mode 100644 index 0000000..75dc82c --- /dev/null +++ b/TransportProcessor.cpp @@ -0,0 +1,419 @@ +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 + +#include "DIAG.h" +// #include "DCCEXParser.h" + +#include "NetworkInterface.h" +#include "HttpRequest.h" +#include "TransportProcessor.h" + +// DCCEXParser ethParser; + +static uint8_t buffer[MAX_ETH_BUFFER]; +static char command[MAX_JMRI_CMD] = {0}; +static uint8_t reply[MAX_ETH_BUFFER]; + +HttpRequest httpReq; +uint16_t _rseq[MAX_SOCK_NUM] = {0}; +uint16_t _sseq[MAX_SOCK_NUM] = {0}; + +char protocolName[4][11] = {"JMRI", "WITHROTTLE", "HTTP", "UNKNOWN"}; // change for Progmem +bool diagNetwork = false; +uint8_t diagNetworkClient = 0; + + +/** + * @brief creates a HttpRequest object for the user callback. Some conditions apply esp reagrding the length of the items in the Request + * can be found in @file HttpRequest.h + * + * @param client Client object from whom we receievd the data + * @param c id of the Client object + */ +void httpProcessor(Connection* c) +{ + uint8_t i, l = 0; + ParsedRequest preq; + l = strlen((char *)buffer); + for (i = 0; i < l; i++) + { + httpReq.parseRequest((char)buffer[i]); + } + if (httpReq.endOfRequest()) + { + preq = httpReq.getParsedRequest(); + httpReq.callback(&preq, c->client); + httpReq.resetRequest(); + } // else do nothing and continue with the next packet +} + +/** + * @brief Set the App Protocol. The detection id done upon the very first message recieved. The client will then be bound to that protocol. Its very brittle + * as e.g. The N message as first message for WiThrottle is not a requirement by the protocol; If any client talking Withrottle doesn't implement this the detection + * will default to JMRI. For HTTP we base this only on a subset of th HTTP verbs which can be used. + * + * @param a First character of the recieved buffer upon first connection + * @param b Second character of the recieved buffer upon first connection + * @return appProtocol + */ +appProtocol setAppProtocol(char a, char b, Connection *c) +{ + appProtocol p; + switch (a) + { + case 'G': // GET + case 'C': // CONNECT + case 'O': // OPTIONS + case 'T': // TRACE + { + p = HTTP; + break; + } + case 'D': // DELETE or D plux hex value + { + if (b == 'E') + { + p = HTTP; + } + else + { + p = WITHROTTLE; + } + break; + } + case 'P': + { + if (b == 'T' || b == 'R') + { + p = WITHROTTLE; + } + else + { + p = HTTP; // PUT / PATCH / POST + } + break; + } + case 'H': + { + if (b == 'U') + { + p = WITHROTTLE; + } + else + { + p = HTTP; // HEAD + } + break; + } + case 'M': + case '*': + case 'R': + case 'Q': // That doesn't make sense as it's the Q or close on app level + case 'N': + { + p = WITHROTTLE; + break; + } + case '<': + { + p = DCCEX; + break; + } + case '#': { + p = DCCEX; + DIAG(F("\nDiagnostics routed to network client\n")); + StringFormatter::setDiagOut(c); + diagNetwork = true; + diagNetworkClient = c->id; + break; + } + default: + { + // here we don't know + p = UNKNOWN_PROTOCOL; + break; + } + } + DIAG(F("\nClient speaks: [%s]\n"), protocolName[p]); + return p; +} + +/** + * @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; + + memset(command, 0, MAX_JMRI_CMD); // clear out the command + DIAG(F("\nBuffer: [%e]\n"), buffer); + // copy overflow into the command + if ((i = strlen(c->overflow)) != 0) + { + // DIAG(F("\nCopy overflow to command: %e"), c->overflow); + strncpy(command, c->overflow, i); + k = i; + } + // reset the overflow + memset(c->overflow, 0, MAX_OVERFLOW); + + // 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 + l = i; + 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 ); + } + + // 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 + 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); + // sendReply(c->client, command, c); + // memset(command, 0, MAX_JMRI_CMD); // clear out the command + + _rseq[c->id]++; + j = 0; + k = 0; + } + else + { + k++; + } + i++; + } +} + +void echoProcessor(Connection *c) +{ + memset(reply, 0, MAX_ETH_BUFFER); + sprintf((char *)reply, "ERROR: malformed content in [%s]", buffer); + if (c->client->connected()) + { + c->client->write(reply, strlen((char *)reply)); + _sseq[c->id]++; + } +} +void jmriProcessor(Connection *c) +{ + processStream(c); + +} +void withrottleProcessor(Connection *c) +{ + processStream(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 + */ + +void TransportProcessor::readStream(Connection *c) +{ + // read bytes from a client + int count = c->client->read(buffer, MAX_ETH_BUFFER - 1); // count is the amount of data ready for reading, -1 if there is no data, 0 is the connection has been closed + buffer[count] = 0; + + // figure out which protocol + + if (!c->isProtocolDefined) + { + c->p = setAppProtocol(buffer[0], buffer[1], c); + c->isProtocolDefined = true; + switch (c->p) + { + case N_DIAG: + case DCCEX: + { + c->delimiter = '>'; + c->appProtocolHandler = (appProtocolCallback)jmriProcessor; + break; + } + case WITHROTTLE: + { + c->delimiter = '\n'; + c->appProtocolHandler = (appProtocolCallback)withrottleProcessor; + break; + } + case HTTP: + { + c->appProtocolHandler = (appProtocolCallback)httpProcessor; + httpReq.callback = NetworkInterface::getHttpCallback(); + break; + } + case UNKNOWN_PROTOCOL: + { + DIAG(F("Requests will not be handeled and packet echoed back\n")); + c->appProtocolHandler = (appProtocolCallback)echoProcessor; + break; + } + } + } + + IPAddress remote = c->client->remoteIP(); + + 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("Client #: [%d]\n"), c->id); + DIAG(F("Packet: [%e]\n"), buffer); + + // chop the buffer into CS / WiThrottle commands || assemble command across buffer read boundaries + c->appProtocolHandler(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) + * + * @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) +{ + DIAG(F("DCC parsing: [%e]\n"), command); + // echo back (as mock parser ) + StringFormatter::send(stream, F("reply to: %s"), command); +} + +/** + * @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(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")); + } +}; + +/* + // 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 new file mode 100644 index 0000000..958b482 --- /dev/null +++ b/TransportProcessor.h @@ -0,0 +1,66 @@ + +/* + * © 2020, Gregor Baues. All rights reserved. + * + * 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 TransportProcessor_h +#define TransportProcessor_h + +#include +#include +#include + + +#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); + +struct Connection { + uint8_t id; + Client* client; + char overflow[MAX_OVERFLOW]; + appProtocol p; + char delimiter = '\0'; + bool isProtocolDefined = false; + appProtocolCallback appProtocolHandler; +}; + + + +class TransportProcessor +{ + +public: + + 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 diff --git a/WifiSetup.cpp b/WifiSetup.cpp new file mode 100644 index 0000000..e9bca8f --- /dev/null +++ b/WifiSetup.cpp @@ -0,0 +1,110 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 + +#include "DIAG.h" +#include "WifiSetup.h" + +bool WifiSetup::setup() { + Serial1.begin(AT_BAUD_RATE); + WiFi.init(Serial1); + + maxConnections = MAX_WIFI_SOCK; + + if (WiFi.status() == WL_NO_MODULE) + { + DIAG(F("Communication with WiFi module failed!\n")); + return 0; + } + + DIAG(F("Waiting for connection to WiFi ")); + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + DIAG(F(".")); + } + // Setup the protocol handler + DIAG(F("\n\nNetwork Protocol: [%s]"), protocol ? "UDP" : "TCP"); + + switch (protocol) + { + case UDP: + { + DIAG(F("\nUDP over Wifi is not yet supported\n")); + connected = false; + /* + udp = new WiFiUDP(); + + maxConnections = 1; + // DIAG(F("Wifi/UDP: [%x:%d]"), udp, port); + if (udp->begin(port)) // no need to call begin for the WiFiEspAT library but doesn't run properly in the context of the application + { + connected = true; + } + else + { + DIAG(F("\nUDP client failed to start")); + connected = false; + } + */ + break; + }; + case TCP: + { + server = new WiFiServer(port); + server->begin(MAX_WIFI_SOCK, 240); + if(server->status()) { + connected = true; + + } else { + DIAG(F("\nWiFi server failed to start")); + connected = false; + } // Connection pool not used for WiFi + break; + }; + case MQTT: { + // do the MQTT setup stuff here + }; + default: + { + DIAG(F("Unkown Ethernet protocol; Setup failed")); + connected = false; + break; + } + } + + if (connected) + { + ip = WiFi.localIP(); + 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 = WiFi.dnsServer1(); + DIAG(F("\nDNS server IP address: [%d.%d.%d.%d] "), dnsip[0], dnsip[1], dnsip[2], dnsip[3]); + DIAG(F("\nNumber of connections: [%d]"), maxConnections); + if( protocol == UDP ) return 0; // no server here as we use UDP + return true; + } + // something went wrong + return false; + +}; + + +WifiSetup::WifiSetup() {} +WifiSetup::WifiSetup(uint16_t p, protocolType pt ) { port = p; protocol = pt; } +WifiSetup::~WifiSetup() {} \ No newline at end of file diff --git a/WifiSetup.h b/WifiSetup.h new file mode 100644 index 0000000..73050c5 --- /dev/null +++ b/WifiSetup.h @@ -0,0 +1,59 @@ +/* + * © 2020 Gregor Baues. All rights reserved. + * + * 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 WifiSetup_h +#define WifiSetup_h + +#include "NetworkSetup.h" +#include "WiFiEspAT.h" + +// Emulate Serial1 on pins 6/7 if not present +#if defined(ARDUINO_ARCH_AVR) && !defined(HAVE_HWSERIAL1) +#include +SoftwareSerial Serial1(6, 7); // RX, TX +#define AT_BAUD_RATE 9600 +#else +#define AT_BAUD_RATE 115200 +#endif + + +class WifiSetup: public NetworkSetup { + +private: + + WiFiServer* server; + WiFiUDP* udp; + +public: + + // WiFiServer *setup(uint16_t port); + bool setup(); + + WiFiUDP* getUdp() { + return udp; + } + + WiFiServer* getServer() { + return server; + } + + WifiSetup(); + WifiSetup(uint16_t port, protocolType protocol); + ~WifiSetup(); +}; + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index f53a690..3e2bfe3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ [platformio] default_envs = mega2560 - uno + ;uno src_dir = . [env] @@ -37,6 +37,7 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 + jandrassy/WiFiEspAT@^1.3.0 monitor_speed = 115200 monitor_flags = --echo @@ -50,6 +51,7 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 + jandrassy/WiFiEspAT@^1.3.0 monitor_speed = 115200 monitor_flags = --echo @@ -63,6 +65,7 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 + jandrassy/WiFiEspAT@^1.3.0 monitor_speed = 115200 monitor_flags = --echo @@ -76,5 +79,6 @@ lib_deps = arduino-libraries/Ethernet SPI mathertel/LiquidCrystal_PCF8574 + jandrassy/WiFiEspAT@^1.3.0 monitor_speed = 115200 monitor_flags = --echo