diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 21487f9..5f1f87e 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -2,38 +2,15 @@ // © 2020, Chris Harlow. All rights reserved. // // This file is a demonstattion of setting up a DCC-EX -// Command station to support direct connection of WiThrottle devices +// Command station with optional support for direct connection of WiThrottle devices // such as "Engine Driver". If you contriol your layout through JMRI // then DON'T connect throttles to this wifi, connect them to JMRI. // -// This is just 3 statements longer than the basic setup. -// -// THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. -// REFER TO SEPARATE EXAMPLE. +// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM. //////////////////////////////////////////////////////////////////////////////////// -// This defines the speed at which the Arduino will communicate with the ESP8266 module. -// Currently only Arduino Mega and 115200 is supported. -#define WIFI_SERIAL_LINK_SPEED 115200 - #include "config.h" -#include "DCC.h" -#include "DIAG.h" -#include "DCCEXParser.h" -#include "version.h" -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) -#include "WifiInterface.h" -#endif -#if ENABLE_FREE_MEM_WARNING -#include "freeMemory.h" -int ramLowWatermark = 32767; // This figure gets overwritten dynamically in loop() -#endif - -#if defined(ARDUINO_ARCH_MEGAAVR) -#include -#endif - - +#include "DCCEX.h" //////////////////////////////////////////////////////////////// @@ -48,74 +25,14 @@ LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_L #endif #endif -// this code is here to demonstrate use of the DCC API and other techniques - -// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from -// the usb or wifi streamm. It demonstrates how a command may be intercepted -// or even a new command created without having to break open the API library code. -// The filter is permitted to use or modify the parameter list before passing it on to -// the standard parser. By setting the opcode to 0, the standard parser will -// just ignore the command on the assumption that you have already handled it. -// -// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup(). -#if ENABLE_CUSTOM_FILTER -void myComandFilter(Print *stream, byte &opcode, byte ¶mCount, int p[]) -{ - (void)stream; // avoid compiler warning if we don't access this parameter - switch (opcode) - { - case '!': // Create a bespoke new command to clear all loco reminders or specific locos e.g - if (paramCount == 0) - DCC::forgetAllLocos(); - else - for (int i = 0; i < paramCount; i++) - DCC::forgetLoco(p[i]); - opcode = 0; // tell parser to ignore this command as we have done it already - break; - default: // drop through and parser will use the command unaltered. - break; - } -} - -// This is an OPTIONAL example of a HTTP filter... -// If you have configured wifi and an HTTP request is received on the Wifi connection -// it will normally be rejected 404 Not Found. - -// If you wish to handle HTTP requests, you can create a filter and ask the WifiInterface to -// call your code for each detected http request. - -void myHttpFilter(Print *stream, byte *cmd) -{ - (void)cmd; // Avoid compiler warning because this example doesnt use this parameter - - // BEWARE - As soon as you start responding, the cmd buffer is trashed! - // You must get everything you need from it before using StringFormatter::send! - - StringFormatter::send(stream, F("HTTP/1.1 200 OK\nContent-Type: text/html\nConnnection: close\n\n")); - StringFormatter::send(stream, F("This is my HTTP filter responding.
")); -} -#endif - -// Callback functions are necessary if you call any API that must wait for a response from the -// programming track. The API must return immediately otherwise other loop() functions would be blocked. -// Your callback function will be invoked when the data arrives from the prog track. -// See the DCC:getLocoId example in the setup function. -#if ENABLE_CUSTOM_CALLBACK -void myCallback(int result) -{ - DIAG(F("\n getting Loco Id callback result=%d"), result); -} -#endif - -// Create a serial command parser... Enables certain diagnostics and commands -// to be issued from the USB serial console -// This is NOT intended for JMRI.... - +// Create a serial command parser for the USB connection, +// This supports JMRI or manual diagnostics and commands +// to be issued from the USB serial console. DCCEXParser serialParser; void setup() { - + //////////////////////////////////////////// // // More display stuff. Need to put this in a .h file and make @@ -156,20 +73,7 @@ void setup() // NOTE: References to Serial1 are for the serial port used to connect // your wifi chip/shield. -// Optionally tell the command parser to use my example filter. -// This will intercept JMRI commands from both USB and Wifi -#if ENABLE_CUSTOM_FILTER - DCCEXParser::setFilter(myComandFilter); -#endif - -#if ENABLE_CUSTOM_CALLBACK - // This is just for demonstration purposes - DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n")); - DCC::getLocoId(myCallback); // myCallback will be called with the result - DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); -#endif - -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); @@ -178,17 +82,21 @@ void setup() Serial1.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); +#if NUM_SERIAL > 1 if (!wifiUp) { Serial2.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port); } +#if NUM_SERIAL > 2 if (!wifiUp) { Serial3.begin(WIFI_SERIAL_LINK_SPEED); wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); } -#endif +#endif // >2 +#endif // >1 +#endif // WIFI_ON // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) @@ -215,12 +123,14 @@ void loop() serialParser.loop(Serial); // Responsibility 3: Optionally handle any incoming WiFi traffic -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#ifdef WIFI_ON WifiInterface::loop(); #endif // Optionally report any decrease in memory (will automatically trigger on first call) #if ENABLE_FREE_MEM_WARNING + static int ramLowWatermark = 32767; // replaced on first loop + int freeNow = freeMemory(); if (freeNow < ramLowWatermark) { diff --git a/DCCEX.h b/DCCEX.h new file mode 100644 index 0000000..091bd47 --- /dev/null +++ b/DCCEX.h @@ -0,0 +1,17 @@ +// This include is intended to visually simplify the .ino for the end users. +// If there were any #ifdefs required they are much better handled in here. + +#ifndef DCCEX_h +#define DCCEX_h + +#include "defines.h" +#include "DCC.h" +#include "DIAG.h" +#include "DCCEXParser.h" +#include "version.h" +#include "WifiInterface.h" +#include "EthernetInterface.h" + +#include + +#endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8b26da7..82cd9d9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. * - * This file is part of Asbelos DCC API + * This file is part of 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 @@ -20,9 +21,6 @@ #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) -#include "WifiInterface.h" -#endif #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" @@ -156,10 +154,15 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) } FILTER_CALLBACK DCCEXParser::filterCallback = 0; +AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; void DCCEXParser::setFilter(FILTER_CALLBACK filter) { filterCallback = filter; } +void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) +{ + atCommandCallback = callback; +} // See documentation on DCC class for info on this section void DCCEXParser::parse(Print *stream, byte *com, bool blocking) @@ -391,11 +394,14 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); DCC::setFn(p[0], p[1], p[2] == 1); return; -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) + case '+': // Complex Wifi interface command (not usual parse) - WifiInterface::ATCommand(com); - return; -#endif + if (atCommandCallback) { + atCommandCallback(com); + return; + } + break; + default: //anything else will diagnose and drop out to DIAG(F("\nOpcode=%c params=%d\n"), opcode, params); for (int i = 0; i < params; i++) diff --git a/DCCEXParser.h b/DCCEXParser.h index e154ff9..5721f6c 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -21,6 +21,7 @@ #include typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]); +typedef void (*AT_COMMAND_CALLBACK)(const byte * command); struct DCCEXParser { @@ -29,6 +30,7 @@ struct DCCEXParser void parse(Print * stream, byte * command, bool blocking); void flush(); static void setFilter(FILTER_CALLBACK filter); + static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); static const int MAX_PARAMS=10; // Must not exceed this private: @@ -59,6 +61,7 @@ struct DCCEXParser static void callback_Vbit(int result); static void callback_Vbyte(int result); static FILTER_CALLBACK filterCallback; + static AT_COMMAND_CALLBACK atCommandCallback; static void funcmap(int cab, byte value, byte fstart, byte fstop); }; diff --git a/DCCWaveform.h b/DCCWaveform.h index 96e5887..b1c6f7d 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -30,7 +30,7 @@ const int MIN_ACK_PULSE_DURATION = 2000; const int MAX_ACK_PULSE_DURATION = 8500; -const int PREAMBLE_BITS_MAIN = 20; +const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp new file mode 100644 index 0000000..3498871 --- /dev/null +++ b/EthernetInterface.cpp @@ -0,0 +1,304 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * 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 new file mode 100644 index 0000000..b702f04 --- /dev/null +++ b/EthernetInterface.h @@ -0,0 +1,107 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * 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/WifiInterface.cpp b/WifiInterface.cpp index 9ea5738..f027ea0 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -1,7 +1,8 @@ /* © 2020, Chris Harlow. All rights reserved. + © 2020, Harald Barth. - This file is part of Asbelos DCC API + This file is part of 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 @@ -17,8 +18,8 @@ along with CommandStation. If not, see . */ +#include "WifiInterface.h" /* config.h and defines.h included here */ #include -#include "WifiInterface.h" #include "DIAG.h" #include "StringFormatter.h" #include "WiThrottle.h" @@ -39,7 +40,7 @@ unsigned long WifiInterface::loopTimeoutStart = 0; int WifiInterface::datalength = 0; int WifiInterface::connectionId; byte WifiInterface::buffer[MAX_WIFI_BUFFER+1]; -MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER); +MemStream * WifiInterface::streamer; Stream * WifiInterface::wifiStream = NULL; HTTP_CALLBACK WifiInterface::httpCallback = 0; @@ -58,7 +59,9 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo checkForOK(200, OK_SEARCH, true); } - + streamer=new MemStream(buffer, MAX_WIFI_BUFFER); + parser.setAtCommandCallback(ATCommand); + DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); return connected; } @@ -298,15 +301,15 @@ void WifiInterface::loop() { case 6: // reading for length if (ch == ':') loopstate = (datalength == 0) ? 99 : 7; // 99 is getout without reading next char else datalength = datalength * 10 + (ch - '0'); - streamer.flush(); // basically sets write point at start of buffer + streamer->flush(); // basically sets write point at start of buffer break; case 7: // reading data - streamer.write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. + streamer->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. // This protects against buffer overflows even with things as innocent // as a browser which send massive, irrlevent HTTP headers. datalength--; if (datalength == 0) { - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later loopstate = 99; } break; @@ -349,8 +352,8 @@ void WifiInterface::loop() { loopstate = 1; } if (ch == 'K') { // assume its in SEND OK - if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending break; @@ -363,7 +366,7 @@ void WifiInterface::loop() { // AT this point we have read an incoming message into the buffer if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer); - streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer + streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer // SIDE EFFECT WARNING::: // We know that parser will read the entire buffer before starting to write to it. // Otherwise we would have to copy the buffer elsewhere and RAM is in short supply. @@ -372,18 +375,18 @@ void WifiInterface::loop() { // Intercept HTTP requests if (isHTTP()) { - if (httpCallback) httpCallback(&streamer, buffer); + if (httpCallback) httpCallback(streamer, buffer); else { StringFormatter::send(streamer, F("HTTP/1.1 404 Not Found\nContent-Type: text/html\nConnnection: close\n\n")); StringFormatter::send(streamer, F("This is not a web server.
")); } closeAfter = true; } - else if (buffer[0] == '<') parser.parse(&streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async + else if (buffer[0] == '<') parser.parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async - else WiThrottle::getThrottle(connectionId)->parse(streamer, buffer); + else WiThrottle::getThrottle(connectionId)->parse(*streamer, buffer); - if (streamer.available() == 0) { + if (streamer->available() == 0) { // No reply if (closeAfter) { if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId); @@ -393,10 +396,10 @@ void WifiInterface::loop() { return; } // prepare to send reply - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later - if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available()); - if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later + if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer->available()); + if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending } diff --git a/WifiInterface.h b/WifiInterface.h index 003ef2d..ecf9005 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. * - * This file is part of Asbelos DCC API + * This file is part of 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 @@ -16,12 +17,10 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - #ifndef WifiInterface_h #define WifiInterface_h #include "DCCEXParser.h" #include "MemStream.h" - #include #include @@ -53,7 +52,6 @@ private: static unsigned long loopTimeoutStart; static const byte MAX_WIFI_BUFFER = 250; static byte buffer[MAX_WIFI_BUFFER + 1]; - static MemStream streamer; + static MemStream * streamer; }; - #endif diff --git a/config.example.h b/config.example.h index 33845f4..bb3e302 100644 --- a/config.example.h +++ b/config.example.h @@ -15,23 +15,18 @@ The configuration file for DCC++ EX Command Station // the correct resistor could damage the sense pin on your Arduino or destroy // the device. // -// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: +// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: +// +// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel +// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) +// FIREBOX_MK1 : The Firebox MK1 +// FIREBOX_MK1S : The Firebox MK1S +// | +// +-----------------------v // -// STANDARD_MOTOR_SHIELD = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) Arduino Motor shield Rev3 based on the L298 -// POLOLU_MOTOR_SHIELD = POLOLU MC33926 MOTOR SHIELD (MAX 28V/2.5 PER CHANNEL) Pololu MC33926 Motor Driver (shield or carrier) -// FUNDUMOTO_SHIELD = FunduMoto Motor Shield -// FIREBOX_MK1 = Firebox MK1 -// FIREBOX_MK1S = Firebox MK1S - - #define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE PROGRAM TRACK CURRENT LIMIT IN MILLIAMPS - -#define TRIP_CURRENT_PROG 250 - ///////////////////////////////////////////////////////////////////////////////////// // // The IP port to talk to a WIFI or Ethernet shield. @@ -57,7 +52,6 @@ The configuration file for DCC++ EX Command Station // // DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP // - //#define IP_ADDRESS { 192, 168, 1, 200 } ///////////////////////////////////////////////////////////////////////////////////// @@ -67,60 +61,9 @@ The configuration file for DCC++ EX Command Station // Uncomment to use with Ethernet Shields // // NOTE: This is not used with ESP8266 WiFi modules. - +// // #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } -///////////////////////////////////////////////////////////////////////////////////// -// -// Allows using a pin as a trigger for a scope or analyzer so we can capture only -// the important parts of the data stream -// -// USE_TRIGGERPIN: Enable code that switches the trigger pin on and off at end -// of the preamble. This takes some clock cycles in the -// interrupt routine for the main track. -// USE_TRIGGERPIN_PER_BIT: As above but for every bit. This only makes sense -// if USE_TRIGGERPIN is set. -// -// The value of the TRIGGERPIN is defined in DCCppEX.h because it might -// be board specific -// -//#define USE_TRIGGERPIN -//#define USE_TRIGGERPIN_PER_BIT - -///////////////////////////////////////////////////////////////////////////////////// -// -// Define only of you need the store to EEPROM feature. This takes RAM and -// you may need to use less MAX_MAIN_REGISTERS to compensate (at least on the UNO) - -#define EESTORE - -///////////////////////////////////////////////////////////////////////////////////// -// -// This shows the status and version at startup. This takes RAM. You can comment -// this line if you need to increase MAX_MAIN_REGISTERS(at least on the UNO) - -#define SHOWCONFIG - -///////////////////////////////////////////////////////////////////////////////////// -// -// This is different from the above config display which only shows one line at startup -// This defines a pin that when jumpered to ground before powering up the Arduinio, -// will display more detailed settings for diagnostics. You must remove the jumper and -// restart the Arduino to return to normal operation - -#define SHOW_CONFIG_DETAIL_PIN A2 - -///////////////////////////////////////////////////////////////////////////////////// -// -// PREAMBLE_MAIN: Length of the preamble on the main track. Per standard this should -// be at least 14 bits but if some equipment wants to insert a RailCom -// cutout this should be at least 16 bits. -// PERAMBLE_PROG: Length of the preamble on the programming track. Per standard this -// should be at least 22 bits - -#define PREAMBLE_MAIN 16 // TODO: Finish configurable preamble code -#define PREAMBLE_PROG 22 - ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE LCD SCREEN USAGE BY THE BASE STATION diff --git a/defines.h b/defines.h new file mode 100644 index 0000000..700687b --- /dev/null +++ b/defines.h @@ -0,0 +1,45 @@ +/* + © 2020, Harald Barth. + + This file is part of 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 . + +*/ + +//////////////////////////////////////////////////////////////////////////////// +// +// WIFI_ON: All prereqs for running with WIFI are met +// +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define WIFI_ON +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// This defines the speed at which the Arduino will communicate with the ESP8266 module. +// Currently only devices which can communicate at 115200 are supported. +// +#define WIFI_SERIAL_LINK_SPEED 115200 + +//////////////////////////////////////////////////////////////////////////////// +// +// Figure out number of serial ports depending on hardware +// +#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define NUM_SERIAL 3 +#endif +#ifndef NUM_SERIAL +#define NUM_SERIAL 1 +#endif diff --git a/objdump.bat b/objdump.bat index c09ebf0..af30cb5 100644 --- a/objdump.bat +++ b/objdump.bat @@ -1,12 +1,14 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt -avr-objdump --private=mem-usage %TMP%\%a%\CommandStation-EX.ino.elf >>%TMP%\OBJDUMP_%a%.txt +SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf + +avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\CommandStation-EX.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt notepad %TMP%\OBJDUMP_%a%.txt EXIT diff --git a/objdump.sh b/objdump.sh index 2e18af2..6e8e7d3 100755 --- a/objdump.sh +++ b/objdump.sh @@ -4,9 +4,10 @@ ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}') PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH -avr-objdump --private=mem-usage /tmp/arduino_build_233823/Blinkhabaplus.ino.elf +LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1) +avr-objdump --private=mem-usage "$LASTBUILD" for segment in .text .data .bss ; do echo '++++++++++++++++++++++++++++++++++' - avr-objdump -x -C /tmp/arduino_build_233823/Blinkhabaplus.ino.elf | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r + avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r done