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