From 7c47bb15620924eb5a6e0a4157b91a3e438c0900 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Tue, 29 Sep 2020 17:19:02 -0400 Subject: [PATCH 01/37] Fix Turnout and Output return value --- CommandStation-EX.ino | 16 +++---- DCC.h | 2 +- DCCEXParser.cpp | 21 ++++++--- Outputs.cpp | 2 +- config.example.h | 106 ------------------------------------------ 5 files changed, 25 insertions(+), 122 deletions(-) delete mode 100644 config.example.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 5f1f87e..41a4c50 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -16,7 +16,7 @@ //////////////////////////////////////////////////////////////// // // Enables an I2C 2x24 or 4x24 LCD Screen -#if ENABLE_LCD +#ifdef ENABLE_LCD bool lcdEnabled = false; #if defined(LIB_TYPE_PCF8574) LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); @@ -37,7 +37,7 @@ void setup() // // More display stuff. Need to put this in a .h file and make // it a class -#if ENABLE_LCD +#ifdef ENABLE_LCD Wire.begin(); // Check that we can find the LCD by its address before attempting to use it. Wire.beginTransmission(LCD_ADDRESS); @@ -45,17 +45,17 @@ void setup() { lcdEnabled = true; lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); - lcdDisplay.setBacklight(255); + lcdDisplay.setBacklight(128); lcdDisplay.clear(); lcdDisplay.setCursor(0, 0); lcdDisplay.print("DCC++ EX v"); lcdDisplay.print(VERSION); lcdDisplay.setCursor(0, 1); -#if COMM_INTERFACE >= 1 - lcdDisplay.print("IP: PENDING"); -#else - lcdDisplay.print("SERIAL: READY"); -#endif +//#if COMM_INTERFACE >= 1 +// lcdDisplay.print("IP: PENDING"); +//#else + lcdDisplay.print("SERIAL: READY 1"); +//#endif #if LCD_LINES > 2 lcdDisplay.setCursor(0, 3); lcdDisplay.print("TRACK POWER: OFF"); diff --git a/DCC.h b/DCC.h index 580d8b7..529e326 100644 --- a/DCC.h +++ b/DCC.h @@ -162,7 +162,7 @@ private: #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif -#if ENABLE_LCD +#ifdef ENABLE_LCD #include #if defined(LIB_TYPE_PCF8574) #include diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8a96e23..075bd32 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -424,7 +424,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) switch (params) { - + case 2: // { Output *o = Output::get(p[0]); @@ -436,11 +436,16 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) return true; case 3: // - Output::create(p[0], p[1], p[2], 1); + if (!Output::create(p[0], p[1], p[2], 1)) + return false; + StringFormatter::send(stream, F("")); return true; case 1: // - return Output::remove(p[0]); + if (!Output::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; case 0: // { @@ -548,12 +553,16 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) switch (params) { case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) - Sensor::create(p[0], p[1], p[2]); + if (!Sensor::create(p[0], p[1], p[2])) + return false; + StringFormatter::send(stream, F("")); return true; case 1: // S id> remove sensor - if (Sensor::remove(p[0])) - return true; + if (!Sensor::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; break; case 0: // lit sensor states diff --git a/Outputs.cpp b/Outputs.cpp index 50a8eda..d72f744 100644 --- a/Outputs.cpp +++ b/Outputs.cpp @@ -90,7 +90,7 @@ void Output::activate(int s){ digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) if(num>0) EEPROM.put(num,data.oStatus); - + } /////////////////////////////////////////////////////////////////////////////// diff --git a/config.example.h b/config.example.h deleted file mode 100644 index bb3e302..0000000 --- a/config.example.h +++ /dev/null @@ -1,106 +0,0 @@ -/********************************************************************** - -Config.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman -COPYRIGHT (c) 2020 Fred Decker - -The configuration file for DCC++ EX Command Station - -**********************************************************************/ - -///////////////////////////////////////////////////////////////////////////////////// -// NOTE: Before connecting these boards and selecting one in this software -// check the quick install guides!!! Some of these boards require a voltage -// generating resitor on the current sense pin of the device. Failure to select -// the correct resistor could damage the sense pin on your Arduino or destroy -// the device. -// -// 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 -// -#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD - -///////////////////////////////////////////////////////////////////////////////////// -// -// The IP port to talk to a WIFI or Ethernet shield. -// -#define IP_PORT 2560 - -///////////////////////////////////////////////////////////////////////////////////// -// -// NOTE: Only supported on Arduino Mega -// Set to false if you not even want it on the Arduino Mega -// -#define ENABLE_WIFI true - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE WiFi Parameters (only in effect if WIFI is on) -// -#define WIFI_SSID "Your network name" -#define WIFI_PASSWORD "Your network passwd" -#define WIFI_HOSTNAME "dccex" - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP -// -//#define IP_ADDRESS { 192, 168, 1, 200 } - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE -// -// Uncomment to use with Ethernet Shields -// -// NOTE: This is not used with ESP8266 WiFi modules. -// -// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE LCD SCREEN USAGE BY THE BASE STATION -// -// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset. -// or one using a Hitachi HD44780. -// -// To enable, uncomment the line below and make sure only the correct LIB_TYPE line -// is uncommented below to select the library used for your LCD backpack - -//#define ENABLE_LCD - -#ifdef ENABLE_LCD - #define LIB_TYPE_PCF8574 - //#define LIB_TYPE_I2C - // This defines the I2C address for the LCD device - #define LCD_ADDRESS 0x27 //common defaults are 0x27 and 0x3F - - // This defines the number of columns the LCD device has - #define LCD_COLUMNS 16 - - // This defines the number of lines the LCD device has - #define LCD_LINES 2 -#endif - - -///////////////////////////////////////////////////////////////////////////////////// -// -// Enable custom command filtering -#define ENABLE_CUSTOM_FILTER false - -///////////////////////////////////////////////////////////////////////////////////// -// -// Enable custom command filtering -#define ENABLE_CUSTOM_CALLBACK false - -///////////////////////////////////////////////////////////////////////////////////// -// -// Enable custom command filtering -#define ENABLE_FREE_MEM_WARNING false From a28792312a134c2b7be31ec77fedb7b5509da9bd Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Tue, 29 Sep 2020 17:28:03 -0400 Subject: [PATCH 02/37] add back config.example.h file --- config.example.h | 106 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 config.example.h diff --git a/config.example.h b/config.example.h new file mode 100644 index 0000000..bb3e302 --- /dev/null +++ b/config.example.h @@ -0,0 +1,106 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman +COPYRIGHT (c) 2020 Fred Decker + +The configuration file for DCC++ EX Command Station + +**********************************************************************/ + +///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Before connecting these boards and selecting one in this software +// check the quick install guides!!! Some of these boards require a voltage +// generating resitor on the current sense pin of the device. Failure to select +// the correct resistor could damage the sense pin on your Arduino or destroy +// the device. +// +// 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 +// +#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD + +///////////////////////////////////////////////////////////////////////////////////// +// +// The IP port to talk to a WIFI or Ethernet shield. +// +#define IP_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only supported on Arduino Mega +// Set to false if you not even want it on the Arduino Mega +// +#define ENABLE_WIFI true + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE WiFi Parameters (only in effect if WIFI is on) +// +#define WIFI_SSID "Your network name" +#define WIFI_PASSWORD "Your network passwd" +#define WIFI_HOSTNAME "dccex" + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// +//#define IP_ADDRESS { 192, 168, 1, 200 } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// +// Uncomment to use with Ethernet Shields +// +// NOTE: This is not used with ESP8266 WiFi modules. +// +// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE LCD SCREEN USAGE BY THE BASE STATION +// +// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset. +// or one using a Hitachi HD44780. +// +// To enable, uncomment the line below and make sure only the correct LIB_TYPE line +// is uncommented below to select the library used for your LCD backpack + +//#define ENABLE_LCD + +#ifdef ENABLE_LCD + #define LIB_TYPE_PCF8574 + //#define LIB_TYPE_I2C + // This defines the I2C address for the LCD device + #define LCD_ADDRESS 0x27 //common defaults are 0x27 and 0x3F + + // This defines the number of columns the LCD device has + #define LCD_COLUMNS 16 + + // This defines the number of lines the LCD device has + #define LCD_LINES 2 +#endif + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_FILTER false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_CALLBACK false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_FREE_MEM_WARNING false From 1c2df3fe22478bf10f20134f52d7e779c9bab04d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 8 Oct 2020 23:39:04 +0200 Subject: [PATCH 03/37] Ack current setable by debug statement D ACK LIMIT n --- DCCEXParser.cpp | 7 ++++++- DCCWaveform.cpp | 11 +++++++---- DCCWaveform.h | 4 ++++ MotorDriver.h | 14 +++++++++----- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4f4030c..5a7c4fc 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -47,6 +47,7 @@ const int HASH_KEYWORD_DCC = 6436; const int HASH_KEYWORD_SLOW = -17209; const int HASH_KEYWORD_PROGBOOST = -6353; const int HASH_KEYWORD_EEPROM = -7168; +const int HASH_KEYWORD_LIMIT = 27413; int DCCEXParser::stashP[MAX_PARAMS]; bool DCCEXParser::stashBusy; @@ -586,7 +587,11 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) break; case HASH_KEYWORD_ACK: // - Diag::ACK = onOff; + if (params >= 2 && p[1] == HASH_KEYWORD_LIMIT) { + DCCWaveform::progTrack.setAckLimit(p[2]); + StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]); + } else + Diag::ACK = onOff; return true; case HASH_KEYWORD_CMD: // diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index a70ab16..bec16b3 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -128,7 +128,7 @@ void DCCWaveform::checkPowerOverload() { if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); - int tripValue= motorDriver->rawCurrentTripValue; + int tripValue= motorDriver->getRawCurrentTripValue(); if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted) tripValue=progTripValue; @@ -292,9 +292,12 @@ int DCCWaveform::getLastCurrent() { // (yes I know I could have subclassed the main track but...) void DCCWaveform::setAckBaseline() { - if (isMainTrack) return; - ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor); - if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->raw2mA(ackThreshold)); + if (isMainTrack) return; + int baseline = motorDriver->getCurrentRaw(); + ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); + if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA threshold=%d/%dmA"), + baseline,motorDriver->raw2mA(baseline), + ackThreshold,motorDriver->raw2mA(ackThreshold)); } void DCCWaveform::setAckPending() { diff --git a/DCCWaveform.h b/DCCWaveform.h index d607420..507cc3d 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -76,6 +76,9 @@ class DCCWaveform { autoPowerOff=false; } }; + inline void setAckLimit(int mA) { + ackLimitmA = mA; + } private: static VirtualTimer * interruptTimer; @@ -118,6 +121,7 @@ class DCCWaveform { bool ackPending; bool ackDetected; int ackThreshold; + int ackLimitmA = 60; int ackMaxCurrent; unsigned long ackCheckStart; // millis unsigned int ackCheckDuration; // millis diff --git a/MotorDriver.h b/MotorDriver.h index cce07ad..cbc09cc 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -29,12 +29,16 @@ class MotorDriver { virtual int getCurrentRaw(); virtual unsigned int raw2mA( int raw); virtual int mA2raw( unsigned int mA); - + inline int getRawCurrentTripValue() { + return rawCurrentTripValue; + } + + private: byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin; - float senseFactor; - unsigned int tripMilliamps; - int rawCurrentTripValue; - const byte UNUSED_PIN = 255; + float senseFactor; + unsigned int tripMilliamps; + int rawCurrentTripValue; + const byte UNUSED_PIN = 255; }; #endif From 0e3046e24fbf1c75e3383393da18b0ee0aaf00f6 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 12 Oct 2020 19:32:47 +0100 Subject: [PATCH 04/37] Lcd experimental (#46) * LCD/OLED Implementation * OLED basic working 32 *132 display * LCD/OLED startup ok * Simplified setup * Missing freememory include * Format Width Allow right padding number width in String format * Intermediate scroll * Compile issues with no LCD * Clean buffers at startup * Support for format left padded numbers * Smarter Scrolling And forced start messages Free Ram in slot 2 * LCD tidying Neater setup block in .ino. Dropped unnecessary code No-scroll if display not full * Missing %E format support This is used in WifiInterface checkForOK * Wifi correction and memory guard --- CommandDistributor.cpp | 18 ++++++++++ CommandDistributor.h | 18 ++++++++++ CommandStation-EX.ino | 60 ++++++++------------------------ DCC.h | 11 ------ DCCEX.h | 3 +- DIAG.h | 1 + LCDDisplay.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++ LCDDisplay.h | 63 +++++++++++++++++++++++++++++++++ LCD_Implementation.h | 55 +++++++++++++++++++++++++++++ LCD_LCD.h | 37 ++++++++++++++++++++ LCD_NONE.h | 27 +++++++++++++++ LCD_OLED.h | 57 ++++++++++++++++++++++++++++++ StringFormatter.cpp | 76 ++++++++++++++++++++++++++++++++++++++-- StringFormatter.h | 4 +++ WiThrottle.cpp | 2 +- WifiInboundHandler.cpp | 6 +++- config.example.h | 36 +++++-------------- 17 files changed, 462 insertions(+), 91 deletions(-) create mode 100644 LCDDisplay.cpp create mode 100644 LCDDisplay.h create mode 100644 LCD_Implementation.h create mode 100644 LCD_LCD.h create mode 100644 LCD_NONE.h create mode 100644 LCD_OLED.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 5b43017..47551b2 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -1,3 +1,21 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * 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 . + */ #include #include "CommandDistributor.h" #include "WiThrottle.h" diff --git a/CommandDistributor.h b/CommandDistributor.h index e27b60e..93e4d7c 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -1,3 +1,21 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * 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 . + */ #ifndef CommandDistributor_h #define CommandDistributor_h #include "DCCEXParser.h" diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 462bd79..9749fea 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,18 +12,6 @@ #include "config.h" #include "DCCEX.h" -//////////////////////////////////////////////////////////////// -// -// Enables an I2C 2x24 or 4x24 LCD Screen -#if ENABLE_LCD -bool lcdEnabled = false; -#if defined(LIB_TYPE_PCF8574) -LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); -#elif defined(LIB_TYPE_I2C) -LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); -#endif -#endif - // 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. @@ -31,45 +19,22 @@ DCCEXParser serialParser; void setup() { -//////////////////////////////////////////// -// -// More display stuff. Need to put this in a .h file and make -// it a class -#if ENABLE_LCD - Wire.begin(); - // Check that we can find the LCD by its address before attempting to use it. - Wire.beginTransmission(LCD_ADDRESS); - if (Wire.endTransmission() == 0) - { - lcdEnabled = true; - lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); - lcdDisplay.setBacklight(255); - lcdDisplay.clear(); - lcdDisplay.setCursor(0, 0); - lcdDisplay.print("DCC++ EX v"); - lcdDisplay.print(VERSION); - lcdDisplay.setCursor(0, 1); -#if COMM_INTERFACE >= 1 - lcdDisplay.print("IP: PENDING"); -#else - lcdDisplay.print("SERIAL: READY"); -#endif -#if LCD_LINES > 2 - lcdDisplay.setCursor(0, 3); - lcdDisplay.print("TRACK POWER: OFF"); -#endif - } -#endif - // The main sketch has responsibilities during setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); + DIAG(F("DCC++ EX v%S"),F(VERSION)); + + CONDITIONAL_LCD_START { + // This block is ignored if LCD not in use + LCD(0,F("DCC++ EX v%S"),F(VERSION)); + LCD(1,F("Starting")); + } // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi -#ifdef WIFI_ON +#if WIFI_ON WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT); #endif // WIFI_ON @@ -83,7 +48,8 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(MOTOR_SHIELD_TYPE); + LCD(1,F("Ready")); } void loop() @@ -101,7 +67,9 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif - + + LCDDisplay::loop(); // ignored if LCD not in use + // 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 @@ -110,7 +78,7 @@ void loop() if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; - DIAG(F("\nFree RAM=%d\n"), ramLowWatermark); + LCD(2,F("Free RAM=%5db"), ramLowWatermark); } #endif } diff --git a/DCC.h b/DCC.h index 580d8b7..4bc0197 100644 --- a/DCC.h +++ b/DCC.h @@ -162,16 +162,5 @@ private: #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif -#if ENABLE_LCD -#include -#if defined(LIB_TYPE_PCF8574) -#include -extern LiquidCrystal_PCF8574 lcdDisplay; -#elif defined(LIB_TYPE_I2C) -#include -extern LiquidCrystal_I2C lcdDisplay; -#endif -extern bool lcdEnabled; -#endif #endif diff --git a/DCCEX.h b/DCCEX.h index 091bd47..fb4baef 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -11,7 +11,8 @@ #include "version.h" #include "WifiInterface.h" #include "EthernetInterface.h" - +#include "LCD_Implementation.h" +#include "freeMemory.h" #include #endif diff --git a/DIAG.h b/DIAG.h index e0d4383..6d16f78 100644 --- a/DIAG.h +++ b/DIAG.h @@ -20,4 +20,5 @@ #define DIAG_h #include "StringFormatter.h" #define DIAG StringFormatter::diag +#define LCD StringFormatter::lcd #endif diff --git a/LCDDisplay.cpp b/LCDDisplay.cpp new file mode 100644 index 0000000..e031f10 --- /dev/null +++ b/LCDDisplay.cpp @@ -0,0 +1,79 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * 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 . + */ + +// CAUTION: the device dependent parts of this class are created in the .ini using LCD_Implementation.h +#include "LCDDisplay.h" + + void LCDDisplay::clear() { + clearNative(); + for (byte row=0;row=MAX_LCD_ROWS || hotCol>=MAX_LCD_COLS) return -1; + rowBuffer[hotRow][hotCol]=b; + hotCol++; + rowBuffer[hotRow][hotCol]=0; + return 1; + } + + void LCDDisplay::loop() { + if (!lcdDisplay) return; + lcdDisplay->loop2(false); + } + + LCDDisplay* LCDDisplay::loop2(bool force) { + if ((!force) && (millis() - lastScrollTime)< LCD_SCROLL_TIME) return NULL; + lastScrollTime=millis(); + clearNative(); + int rowFirst=nextFilledRow(); + if (rowFirst<0)return NULL; // No filled rows + setRowNative(0); + writeNative(rowBuffer[rowFirst]); + for (int slot=1;slot. + */ +#ifndef LCDDisplay_h +#define LCDDisplay_h +#include + +// This class is created in LCDisplay_Implementation.h + +class LCDDisplay : public Print { + + public: + static const int MAX_LCD_ROWS=8; + static const int MAX_LCD_COLS=16; + static const long LCD_SCROLL_TIME=3000; // 3 seconds + + static LCDDisplay* lcdDisplay; + LCDDisplay(); + void interfake(int p1, int p2, int p3); + + // Internally handled functions + static void loop(); + LCDDisplay* loop2(bool force); + void setRow(byte line); + void clear(); + + virtual size_t write(uint8_t b); + using Print::write; + + private: + int nextFilledRow(); + + // Relay functions to the live driver + void clearNative(); + void displayNative(); + void setRowNative(byte line); + void writeNative(char * b); + + unsigned long lastScrollTime=0; + int hotRow=0; + int hotCol=0; + int topRow=0; + int lcdRows; + void renderRow(byte row); + char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS+1]; +}; + +#endif diff --git a/LCD_Implementation.h b/LCD_Implementation.h new file mode 100644 index 0000000..1da344e --- /dev/null +++ b/LCD_Implementation.h @@ -0,0 +1,55 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * 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 . + */ + +//////////////////////////////////////////////////////////////////////////////////// +// This implementation is designed to be #included ONLY ONCE in the .ino +// +// It will create a driver implemntation and a shim class implementation. +// This means that other classes can reference the shim without knowing +// which libraray is involved. +//////////////////////////////////////////////////////////////////////////////////// + +#include "config.h" +#include +#include "LCDDisplay.h" + +LCDDisplay * LCDDisplay::lcdDisplay=0; + +// Implement the LCDDisplay shim class as a singleton. +// Notice that the LCDDisplay class declaration (LCDDisplay.h) is independent of the library +// but the implementation is compiled here with dependencies on LCDDriver which is +// specific to the library in use. +// Thats the workaround to the drivers not all implementing a common interface. + +#if defined(OLED_DRIVER) + #include "LCD_OLED.h" + #define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true)) + + +#elif defined(LCD_DRIVER) + #include "LCD_LCD.h" + #define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true)) + +#else + #include "LCD_NONE.h" + #define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */ +#endif + + + diff --git a/LCD_LCD.h b/LCD_LCD.h new file mode 100644 index 0000000..6f88ddc --- /dev/null +++ b/LCD_LCD.h @@ -0,0 +1,37 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * 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 . + */ + #include + LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows + // DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER + LCDDisplay::LCDDisplay() { + lcdDisplay=this; + LCDDriver.init(); + LCDDriver.backlight(); + interfake(LCD_DRIVER); + clear(); + } + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; } + void LCDDisplay::clearNative() {LCDDriver.clear();} + void LCDDisplay::setRowNative(byte row) { + LCDDriver.setCursor(0, row); + LCDDriver.print(F(" ")); + LCDDriver.setCursor(0, row); + } + void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); } + void LCDDisplay::displayNative() { LCDDriver.display(); } diff --git a/LCD_NONE.h b/LCD_NONE.h new file mode 100644 index 0000000..65e05e4 --- /dev/null +++ b/LCD_NONE.h @@ -0,0 +1,27 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * 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 . + */ + +// dummy LCD shim to keep linker happy + LCDDisplay::LCDDisplay() {} + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;} + void LCDDisplay::setRowNative(byte row) { (void)row;} + void LCDDisplay::clearNative() {} + void LCDDisplay::writeNative(char * b){ (void)b;} // + void LCDDisplay::displayNative(){} + diff --git a/LCD_OLED.h b/LCD_OLED.h new file mode 100644 index 0000000..efa2bc7 --- /dev/null +++ b/LCD_OLED.h @@ -0,0 +1,57 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * + * 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 . + */ + +// OLED Implementation of LCDDisplay class +// Note: this file is optionally included by LCD_Implenentation.h +// It is NOT a .cpp file to prevent it being compiled and demanding libraraies even when not needed. + +#include +Adafruit_SSD1306 LCDDriver(OLED_DRIVER); + +// DEVICE SPECIFIC LCDDisplay Implementation for OLED + +LCDDisplay::LCDDisplay() { + if(LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3C) || LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { + DIAG(F("\nOLED display found")); + delay(2000); // painful Adafruit splash pants! + lcdDisplay=this; + LCDDriver.setTextSize(1); // Normal 1:1 pixel scale + LCDDriver.setTextColor(SSD1306_WHITE); // Draw white text + interfake(OLED_DRIVER,0); + clear(); + return; + } + DIAG(F("\nOLED display not found\n")); + } + + void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; lcdRows=p2/8; (void)p3;} + + void LCDDisplay::clearNative() {LCDDriver.clearDisplay();} + + void LCDDisplay::setRowNative(byte row) { + // Positions text write to start of row 1..n and clears previous text + int y=8*row; + LCDDriver.fillRect(0, y, LCDDriver.width(), 8, SSD1306_BLACK); + LCDDriver.setCursor(0, y); + } + + void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); } + + void LCDDisplay::displayNative() { LCDDriver.display(); } + diff --git a/StringFormatter.cpp b/StringFormatter.cpp index e99cfc3..261794a 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -31,6 +31,8 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" + bool Diag::ACK=false; bool Diag::CMD=false; bool Diag::WIFI=false; @@ -44,6 +46,14 @@ void StringFormatter::diag( const __FlashStringHelper* input...) { send2(diagSerial,input,args); } +void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { + if (!LCDDisplay::lcdDisplay) return; + LCDDisplay::lcdDisplay->setRow(row); + va_list args; + va_start(args, input); + send2(LCDDisplay::lcdDisplay,input,args); +} + void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) { va_list args; va_start(args, input); @@ -56,7 +66,6 @@ void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) send2(&stream,input,args); } - void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) { // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output @@ -66,6 +75,13 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va char c=pgm_read_byte_near(flash+i); if (c=='\0') return; if(c!='%') { stream->print(c); continue; } + + bool formatContinues=false; + byte formatWidth=0; + bool formatLeft=false; + do { + + formatContinues=false; i++; c=pgm_read_byte_near(flash+i); switch(c) { @@ -73,14 +89,34 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va case 'c': stream->print((char) va_arg(args, int)); break; case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; + case 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break; case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break; - case 'd': stream->print(va_arg(args, int), DEC); break; - case 'l': stream->print(va_arg(args, long), DEC); break; + case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; + case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print(va_arg(args, int), HEX); break; case 'f': stream->print(va_arg(args, double), 2); break; + //format width prefix + case '-': + formatLeft=true; + formatContinues=true; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + formatWidth=formatWidth * 10 + (c-'0'); + formatContinues=true; + break; } + } while(formatContinues); } va_end(args); } @@ -94,6 +130,17 @@ void StringFormatter::printEscapes(Print * stream,char * input) { } } +void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) { + + if (!stream) return; + char* flash=(char*)input; + for(int i=0; ; ++i) { + char c=pgm_read_byte_near(flash+i); + printEscape(stream,c); + if (c=='\0') return; + } +} + void StringFormatter::printEscape( char c) { printEscape(diagSerial,c); } @@ -109,4 +156,27 @@ void StringFormatter::printEscape(Print * stream, char c) { default: stream->print(c); } } + + +void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) { + if (width==0) { + stream->print(value, DEC); + return; + } + + int digits=(value <= 0)? 1: 0; // zero and negative need extra digot + long v=value; + while (v) { + v /= 10; + digits++; + } + + if (formatLeft) stream->print(value, DEC); + while(digitsprint(' '); + digits++; + } + if (!formatLeft) stream->print(value, DEC); + } + diff --git a/StringFormatter.h b/StringFormatter.h index 62d2492..5cbccf1 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -27,6 +27,7 @@ #define __FlashStringHelper char #endif +#include "LCDDisplay.h" class Diag { public: static bool ACK; @@ -42,16 +43,19 @@ 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); 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/WiThrottle.cpp b/WiThrottle.cpp index 044b00b..5c536b5 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -103,7 +103,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) { // we have to take a copy of the cmd buffer as the reply will get built into the cmdx byte local[150]; - for (byte i=0;i Date: Mon, 12 Oct 2020 22:18:09 +0100 Subject: [PATCH 05/37] DIAG LCD --- StringFormatter.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 261794a..2ecefec 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -47,9 +47,16 @@ void StringFormatter::diag( const __FlashStringHelper* input...) { } void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { + va_list args; + + // Issue the LCD as a diag first + diag(F("\nLCD%d:"),row); + va_start(args, input); + send2(diagSerial,input,args); + diag(F("\n")); + if (!LCDDisplay::lcdDisplay) return; LCDDisplay::lcdDisplay->setRow(row); - va_list args; va_start(args, input); send2(LCDDisplay::lcdDisplay,input,args); } From 3fe04c1a0eb4e81c2d715a41cadeb6bf8030b414 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 12 Oct 2020 22:18:55 +0100 Subject: [PATCH 06/37] DCC getFn --- DCC.cpp | 9 +++++++++ DCC.h | 1 + 2 files changed, 10 insertions(+) diff --git a/DCC.cpp b/DCC.cpp index ca563ac..972dfa3 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -155,6 +155,15 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) { return funcstate; } +int DCC::getFn( int cab, byte functionNumber) { + if (cab<=0 || functionNumber>28) return -1; // unknown + int reg = lookupSpeedTable(cab); + if (reg<0) return -1; + + unsigned long funcmask = (1UL< Date: Mon, 12 Oct 2020 22:21:37 +0100 Subject: [PATCH 07/37] Ckean lost buffer --- WifiInterface.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WifiInterface.h b/WifiInterface.h index b2bdd20..d474716 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -50,8 +50,6 @@ private: static int datalength; static int connectionId; 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 From b98c853a1b4ea026e018e86f798349c146133df0 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 12 Oct 2020 22:24:37 +0100 Subject: [PATCH 08/37] Withrotthe Functions --- WiThrottle.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 5c536b5..ca706fe 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -239,8 +239,11 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){ myLocos[loco].throttle=throttleChar; myLocos[loco].cab=locoid; StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco - // TODO... get known Fn states from DCC (need memoryStream improvements to handle data length) - // for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],fkey); + //Get known Fn states from DCC + for(int fKey=0; fKey<=12; fKey++) { + int fstate=DCC::getFn(locoid,fKey); + if (fstate>=0) StringFormatter::send(stream,F("M%cA%c<;>F%d%d\n"),throttleChar,cmd[3],fstate,fKey); + } StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid))); StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid)); StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 From 11e22c5d1d55a32cc484e4b74246b54c91853d60 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 13 Oct 2020 17:37:40 +0100 Subject: [PATCH 09/37] Working Wifi ringbuffer implementation Notice 1kb output buffer Aslo no need to copy command in Withrottle --- CommandDistributor.cpp | 45 +-------------- CommandDistributor.h | 9 +-- RingStream.cpp | 70 +++++++++++++++++++++++ RingStream.h | 46 +++++++++++++++ WiThrottle.cpp | 10 +--- WifiInboundHandler.cpp | 123 ++++++++++++++++++----------------------- WifiInboundHandler.h | 32 ++++------- 7 files changed, 184 insertions(+), 151 deletions(-) create mode 100644 RingStream.cpp create mode 100644 RingStream.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 47551b2..4d13f40 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -22,51 +22,10 @@ DCCEXParser * CommandDistributor::parser=0; -bool CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) { - - - // 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. - - - bool closeAfter=false; - // Intercept HTTP requests - if (isHTTP(buffer)) { - if (httpCallback) httpCallback(streamer, buffer); - closeAfter = true; - } - else if (buffer[0] == '<') { +void CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) { + if (buffer[0] == '<') { if (!parser) parser = new DCCEXParser(); parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async } else WiThrottle::getThrottle(clientId)->parse(*streamer, buffer); - - return closeAfter; } - -bool CommandDistributor::isHTTP(byte * buffer) { - - // POST GET PUT PATCH DELETE - // You may think a simple strstr() is better... but not when ram & time is in short supply - switch (buffer[0]) { - case 'P': - if (buffer[1] == 'U' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; - if (buffer[1] == 'O' && buffer[2] == 'S' && buffer[3] == 'T' && buffer[4] == ' ') return true; - if (buffer[1] == 'A' && buffer[2] == 'T' && buffer[3] == 'C' && buffer[4] == 'H' && buffer[5] == ' ') return true; - return false; - case 'G': - if (buffer[1] == 'E' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; - return false; - case 'D': - if (buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'E' && buffer[4] == 'T' && buffer[5] == 'E' && buffer[6] == ' ') return true; - return false; - default: - return false; - } -} - -void CommandDistributor::setHTTPCallback(HTTP_CALLBACK callback) { - httpCallback = callback; -} -HTTP_CALLBACK CommandDistributor::httpCallback=0; diff --git a/CommandDistributor.h b/CommandDistributor.h index 93e4d7c..2bec81b 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -20,18 +20,11 @@ #define CommandDistributor_h #include "DCCEXParser.h" -typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd); - class CommandDistributor { public : - static void setHTTPCallback(HTTP_CALLBACK callback); - static bool parse(byte clientId,byte* buffer, Print * streamer); - - + static void parse(byte clientId,byte* buffer, Print * streamer); private: - static HTTP_CALLBACK httpCallback; - static bool isHTTP(byte * buffer); static DCCEXParser * parser; }; diff --git a/RingStream.cpp b/RingStream.cpp new file mode 100644 index 0000000..d026e9b --- /dev/null +++ b/RingStream.cpp @@ -0,0 +1,70 @@ +/* + + (c) 2015 Ingo Fischer + buffer serial device + based on Arduino SoftwareSerial + + Constructor warning messages fixed by Chris Harlow. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "RingStream.h" +#include "DIAG.h" + +RingStream::RingStream( const uint16_t len) +{ + _len=len; + _buffer=new byte[len]; + _pos_write=0; + _pos_read=0; + _buffer[0]=0; + _overflow=false; +} + +size_t RingStream::write(uint8_t byte) { + if (_overflow) return 0; + _buffer[_pos_write] = byte; + ++_pos_write; + if (_pos_write>=_len) _pos_write=0; + if (_pos_write==_pos_read) { + _overflow=true; + DIAG(F("\nRingStream(%d) OVERFLOW %d %d \n"),_len, _pos_write, _pos_read); + return 0; + } + return 1; +} + +int RingStream::read() { + if (_pos_read==_pos_write) return -1; + byte b=_buffer[_pos_read]; + _pos_read++; + if (_pos_read>=_len) _pos_read=0; + _overflow=false; + return b; +} + + +int RingStream::count() { + int peek=_pos_read; + int counter=0; + while(_buffer[peek]) { + counter++; + peek++; + if (peek >= _len) peek=0; + } + return counter; + } diff --git a/RingStream.h b/RingStream.h new file mode 100644 index 0000000..dee00b4 --- /dev/null +++ b/RingStream.h @@ -0,0 +1,46 @@ +#ifndef RingStream_h +#define RingStream_h +/* + + (c) 2015 Ingo Fischer + buffer serial device + based on Arduino SoftwareSerial + + Constructor warning messages fixed by Chris Harlow. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include + +class RingStream : public Print { + + public: + RingStream( const uint16_t len); + + virtual size_t write(uint8_t b); + using Print::write; + int read(); + int count(); + + private: + int _len; + int _pos_write; + int _pos_read; + bool _overflow; + byte * _buffer; +}; + +#endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index ca706fe..a0ec254 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -101,15 +101,7 @@ WiThrottle::~WiThrottle() { void WiThrottle::parse(Print & stream, byte * cmdx) { - // we have to take a copy of the cmd buffer as the reply will get built into the cmdx - byte local[150]; - for (byte i=0;i #include "WifiInboundHandler.h" +#include "RingStream.h" #include "CommandDistributor.h" #include "DIAG.h" @@ -16,14 +17,10 @@ void WifiInboundHandler::loop() { WifiInboundHandler::WifiInboundHandler(Stream * ESStream) { wifiStream=ESStream; - for (int clientId=0;clientIdavailable() - clientBuffer[clientId]=new byte[MAX_WIFI_BUFFER+1]; - clientStream[clientId]=new MemStream(clientBuffer[clientId], MAX_WIFI_BUFFER); - } clientPendingCIPSEND=-1; + inboundRing=new RingStream(INBOUND_RING); + outboundRing=new RingStream(OUTBOUND_RING); + pendingCipsend=false; } @@ -31,38 +28,50 @@ WifiInboundHandler::WifiInboundHandler(Stream * ESStream) { // +IPD,x,lll:data is stored in streamer[x] // Other input returns void WifiInboundHandler::loop1() { - - // First handle all inbound traffic events - if (loop2()!=INBOUND_IDLE) return; + // First handle all inbound traffic events because they will block the sending + if (loop2()!=INBOUND_IDLE) return; // if nothing is already CIPSEND pending, we can CIPSEND one reply if (clientPendingCIPSEND<0) { - for (int clientId=0;clientIdavailable()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientId, clientStream[clientId]->available()); - clientStatus[clientId]=CIPSEND_PENDING; - return; + int next=outboundRing->read(); + if (next>=0) { + currentReplySize=outboundRing->count(); + if (currentReplySize==0) { + outboundRing->read(); // drop end marker } - } - } - - // if something waiting to close we can call one of them - - for (int clientId=0;clientIdread(); + if (next>0) { + int clientId=next-'0'; //convert char to int + int count=inboundRing->count(); + if (Diag::WIFI) DIAG(F("\nExec waiting %d %d:"),clientId,count); + byte cmd[count+1]; + for (int i=0;iread(); + cmd[count]=0; + if (Diag::WIFI) DIAG(F("%e\n"),cmd); + + outboundRing->print(clientId); + CommandDistributor::parse(clientId,cmd,outboundRing); + outboundRing->write((byte)0); return; } } -} + // This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor @@ -86,10 +95,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } if (ch=='>') { - if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),clientStream[clientPendingCIPSEND]->available()); - wifiStream->write(clientBuffer[clientPendingCIPSEND], clientStream[clientPendingCIPSEND]->available()); - clientStatus[clientPendingCIPSEND]=clientCloseAfterReply[clientPendingCIPSEND]? CLOSE_AFTER_SEND: UNUSED; + if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),currentReplySize); + for (int i=0;iwrite(outboundRing->read()); + outboundRing->read(); // drop the end marker clientPendingCIPSEND=-1; + pendingCipsend=false; loopState=SKIPTOEND; break; } @@ -100,15 +110,12 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND - if (clientPendingCIPSEND>=0) { - clientStatus[clientPendingCIPSEND]=REPLY_PENDING; - clientPendingCIPSEND=-1; - } + pendingCipsend=(clientPendingCIPSEND>=0); loopState=SKIPTOEND; break; } - if (ch>='0' && ch<=('0'+MAX_CLIENTS)) { + if (ch>='0' && ch<='9') { runningClientId=ch-'0'; loopState=GOT_CLIENT_ID; break; @@ -133,7 +140,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { break; case IPD4_CLIENT: // reading connection id - if (ch >= '0' || ch <('0'+MAX_CLIENTS)){ + if (ch >= '0' || ch <='9'){ runningClientId=ch-'0'; loopState=IPD5; } @@ -151,8 +158,8 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { loopState=ANYTHING; break; } - clientStream[runningClientId]->flush(); // prepare streamer for input - clientStatus[runningClientId]=INBOUND_ARRIVING; + if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength); + inboundRing->print(runningClientId); // prefix inbound with client id loopState=IPD_DATA; break; } @@ -160,12 +167,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { break; case IPD_DATA: // reading data - clientStream[runningClientId]->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. + inboundRing->write(ch); dataLength--; if (dataLength == 0) { - clientStatus[runningClientId]=READY_TO_PROCESS; + inboundRing->write((byte)0); loopState = ANYTHING; } break; @@ -181,8 +186,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { case GOT_CLIENT_ID3: // got "x C" before CLOSE or CONNECTED (which is ignored) if(ch=='L') { // CLOSE - clientStatus[runningClientId]=UNUSED; - if (runningClientId==clientPendingCIPSEND) clientPendingCIPSEND=-1; + if (runningClientId==clientPendingCIPSEND) { + // clear the outbound for this client + for (int i=0;i<=currentReplySize;i++) outboundRing->read(); + } } loopState=SKIPTOEND; break; @@ -194,25 +201,3 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } // available return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY; } - - -void WifiInboundHandler::processCommand(byte clientId) { - clientStatus[clientId]=PROCESSING; - byte * buffer=clientBuffer[clientId]; - MemStream * streamer=clientStream[clientId]; - buffer[streamer->available()]='\0'; - - if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),clientId, buffer); - streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer - - clientCloseAfterReply[clientId]=CommandDistributor::parse(clientId,buffer,streamer); - - if (streamer->available() == 0) { - clientStatus[clientId]=UNUSED; - } - else { - 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(), clientId, buffer, streamer->available()); - clientStatus[clientId]=REPLY_PENDING; - } -} diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index 87ac89a..fa41aa4 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -1,8 +1,7 @@ #ifndef WifiInboundHandler_h #define WifiInboundHandler_h -#include "MemStream.h" -#include "DCCEXParser.h" +#include "RingStream.h" #include "DIAG.h" class WifiInboundHandler { @@ -14,9 +13,7 @@ class WifiInboundHandler { static WifiInboundHandler * singleton; - static const byte MAX_CLIENTS=5; - static const byte MAX_WIFI_BUFFER=255; - + enum INBOUND_STATE { INBOUND_BUSY, // keep calling in loop() INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND @@ -41,32 +38,23 @@ class WifiInboundHandler { GOT_CLIENT_ID3 // clientid prefix to CONNECTED / CLOSED }; - enum CLIENT_STATUS { - UNUSED, // client slot not in use - INBOUND_ARRIVING, // data is arriving - READY_TO_PROCESS, // data has arrived, may call parser now - PROCESSING, // command in progress - REPLY_PENDING, // reply is ready to CIPSEND - CIPSEND_PENDING, // CIPSEND waiting for > - CLOSE_PENDING, // CLOSE received - CLOSE_AFTER_SEND // Send CLOSE after CIPSEND completed - }; WifiInboundHandler(Stream * ESStream); void loop1(); INBOUND_STATE loop2(); - void processCommand(byte clientId); Stream * wifiStream; - DCCEXParser *parser; - + static const int INBOUND_RING = 200; + static const int OUTBOUND_RING = 1024; + + RingStream * inboundRing; + RingStream * outboundRing; + LOOP_STATE loopState=ANYTHING; int runningClientId; // latest client inbound processing data or CLOSE int dataLength; // dataLength of +IPD - byte * clientBuffer[MAX_CLIENTS]; - MemStream * clientStream[MAX_CLIENTS]; - CLIENT_STATUS clientStatus[MAX_CLIENTS]; - bool clientCloseAfterReply[MAX_CLIENTS]; int clientPendingCIPSEND=-1; + int currentReplySize; + bool pendingCipsend; }; #endif From ae77c5b7d6bcd9847b2abc27d177ad2b8d3272b8 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 13 Oct 2020 18:01:11 +0100 Subject: [PATCH 10/37] Send all functions --- WiThrottle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index a0ec254..ac503a0 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -232,7 +232,7 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){ myLocos[loco].cab=locoid; StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco //Get known Fn states from DCC - for(int fKey=0; fKey<=12; fKey++) { + for(int fKey=0; fKey<=28; fKey++) { int fstate=DCC::getFn(locoid,fKey); if (fstate>=0) StringFormatter::send(stream,F("M%cA%c<;>F%d%d\n"),throttleChar,cmd[3],fstate,fKey); } From 7dca6db1c57acc178d79025f2cb0113c75b5d412 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 13 Oct 2020 23:08:19 +0200 Subject: [PATCH 11/37] Bit shuffling for function groups --- DCC.cpp | 6 +++--- DCCEXParser.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index ca563ac..401f545 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -456,15 +456,15 @@ bool DCC::issueReminder(int reg) { break; case 1: // remind function group 1 (F0-F4) if (flags & FN_GROUP_1) - setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); + setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD break; case 2: // remind function group 2 F5-F8 if (flags & FN_GROUP_2) - setFunctionInternal(loco,0, 176 + ((functions>>5)& 0x0F)); + setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD break; case 3: // remind function group 3 F9-F12 if (flags & FN_GROUP_3) - setFunctionInternal(loco,0, 160 + ((functions>>9)& 0x0F)); + setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD break; case 4: // remind function group 4 F13-F20 if (flags & FN_GROUP_4) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 5a7c4fc..ece2842 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -466,19 +466,19 @@ bool DCCEXParser::parsef(Print *stream, int params, int p[]) // convenient for other processing if (params == 2) { - byte groupcode = p[1] & 0xE0; - if (groupcode == 0x80) + byte instructionField = p[1] & 0xE0; // 1110 0000 + if (instructionField == 0x80) // 1000 0000 Function group 1 { + // Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0 byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); funcmap(p[0], normalized, 0, 4); } - else if (groupcode == 0xC0) + else if (instructionField == 0xA0) // 1010 0000 Function group 2 { - funcmap(p[0], p[1], 5, 8); - } - else if (groupcode == 0xA0) - { - funcmap(p[0], p[1], 9, 12); + if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12 + funcmap(p[0], p[1], 5, 8); + else + funcmap(p[0], p[1], 9, 12); } } if (params == 3) From 4dee6da29ef59f512b5e9bdc8c99a335649f5362 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 20 Oct 2020 10:55:12 +0200 Subject: [PATCH 12/37] make volatile because of interrupt routine and optimzer in PIO --- DCCWaveform.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DCCWaveform.h b/DCCWaveform.h index 507cc3d..32041e6 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -118,8 +118,8 @@ class DCCWaveform { unsigned int power_good_counter = 0; // ACK management (Prog track only) - bool ackPending; - bool ackDetected; + volatile bool ackPending; + volatile bool ackDetected; int ackThreshold; int ackLimitmA = 60; int ackMaxCurrent; From 8c8a4846716762ec91db77cfa5737cb46d0b0cfd Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 20 Oct 2020 12:48:32 +0100 Subject: [PATCH 13/37] License details correction --- RingStream.cpp | 39 +++++++++++++++++---------------------- RingStream.h | 38 +++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index d026e9b..eee0753 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -1,26 +1,21 @@ /* - - (c) 2015 Ingo Fischer - buffer serial device - based on Arduino SoftwareSerial - - Constructor warning messages fixed by Chris Harlow. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library 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 -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -*/ + * © 2020, 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 . + */ #include "RingStream.h" #include "DIAG.h" diff --git a/RingStream.h b/RingStream.h index dee00b4..7a129e0 100644 --- a/RingStream.h +++ b/RingStream.h @@ -1,28 +1,24 @@ #ifndef RingStream_h #define RingStream_h /* + * © 2020, 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 . + */ - (c) 2015 Ingo Fischer - buffer serial device - based on Arduino SoftwareSerial - - Constructor warning messages fixed by Chris Harlow. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library 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 -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -*/ #include class RingStream : public Print { From c882c2edfc100325072c16ea38df50b8908716e6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 22 Oct 2020 20:38:10 +0200 Subject: [PATCH 14/37] prepare version for release --- version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 6e45e70..d0c09b9 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" // const char VERSION[] PROGMEM ="0.2.0"; -#define VERSION "0.2.0" +#define VERSION "3.0.0" -#endif \ No newline at end of file +#endif From a16b3a1fa67e14118cd93a2ffd0c1a61563db9ef Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 22 Oct 2020 20:24:47 +0100 Subject: [PATCH 15/37] Drop obsolete reference --- WifiInterface.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/WifiInterface.h b/WifiInterface.h index d474716..57dd469 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -20,7 +20,6 @@ #ifndef WifiInterface_h #define WifiInterface_h #include "DCCEXParser.h" -#include "MemStream.h" #include #include @@ -50,6 +49,5 @@ private: static int datalength; static int connectionId; static unsigned long loopTimeoutStart; - static MemStream * streamer; }; #endif From 9099af31883f6fed1341a9544fa833be78609594 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 25 Oct 2020 23:52:22 +0100 Subject: [PATCH 16/37] Sensors show their status at change (as in classic) --- DCCEXParser.cpp | 8 +++----- Sensors.cpp | 26 ++++++++++++++++++++++++-- Sensors.h | 3 ++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index ece2842..e958ce5 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -96,6 +96,7 @@ void DCCEXParser::loop(Stream &stream) buffer[bufferLength++] = ch; } } + Sensor::checkAll(&stream); // Update and print changes } int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) @@ -359,11 +360,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; case 'Q': // SENSORS - Sensor::checkAll(); - for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) - { - StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum); - } + Sensor::checkAll(NULL); // Update, don't print changes + Sensor::printAll(stream); // Print all return; case 's': // diff --git a/Sensors.cpp b/Sensors.cpp index 3f55c5f..e0913a2 100644 --- a/Sensors.cpp +++ b/Sensors.cpp @@ -65,27 +65,49 @@ decide to ignore the return and only react to triggers. **********************************************************************/ - +#include "StringFormatter.h" #include "Sensors.h" #include "EEStore.h" +/////////////////////////////////////////////////////////////////////////////// +// +// checks all defined sensors and prints _changed_ sensor states +// to stream unless stream is NULL in which case only internal +// state is updated +// /////////////////////////////////////////////////////////////////////////////// -void Sensor::checkAll(){ +void Sensor::checkAll(Print *stream){ for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; if(!tt->active && tt->signal<0.5){ tt->active=true; + if (stream != NULL) StringFormatter::send(stream, F(""), tt->data.snum); } else if(tt->active && tt->signal>0.9){ tt->active=false; + if (stream != NULL) StringFormatter::send(stream, F(""), tt->data.snum); } } // loop over all sensors } // Sensor::checkAll +/////////////////////////////////////////////////////////////////////////////// +// +// prints all sensor states to stream +// +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::printAll(Print *stream){ + + for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + if (stream != NULL) + StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum); + } // loop over all sensors +} // Sensor::printAll + /////////////////////////////////////////////////////////////////////////////// Sensor *Sensor::create(int snum, int pin, int pullUp){ diff --git a/Sensors.h b/Sensors.h index 71f60ba..717da69 100644 --- a/Sensors.h +++ b/Sensors.h @@ -40,7 +40,8 @@ struct Sensor{ static Sensor *create(int, int, int); static Sensor* get(int); static bool remove(int); - static void checkAll(); + static void checkAll(Print *); + static void printAll(Print *); }; // Sensor #endif From ecd176042ecbaa5047ab0f6cbf1fb0a9e8753f3c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Oct 2020 00:56:25 +0100 Subject: [PATCH 17/37] checkAll() only checks one now (and should be renamed) --- DCCEXParser.cpp | 3 +-- Sensors.cpp | 31 +++++++++++++++++-------------- Sensors.h | 1 + 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e958ce5..712313c 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -360,8 +360,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; case 'Q': // SENSORS - Sensor::checkAll(NULL); // Update, don't print changes - Sensor::printAll(stream); // Print all + Sensor::printAll(stream); return; case 's': // diff --git a/Sensors.cpp b/Sensors.cpp index e0913a2..b9a9c9d 100644 --- a/Sensors.cpp +++ b/Sensors.cpp @@ -72,26 +72,27 @@ decide to ignore the return and only react to triggers. /////////////////////////////////////////////////////////////////////////////// // -// checks all defined sensors and prints _changed_ sensor states +// checks one defined sensors and prints _changed_ sensor state // to stream unless stream is NULL in which case only internal -// state is updated +// state is updated. Then advances to next sensor which will +// be checked att next invocation. // /////////////////////////////////////////////////////////////////////////////// void Sensor::checkAll(Print *stream){ + + if (firstSensor == NULL) return; + if (readingSensor == NULL) readingSensor=firstSensor; + readingSensor->signal=readingSensor->signal*(1.0-SENSOR_DECAY)+digitalRead(readingSensor->data.pin)*SENSOR_DECAY; - for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; - - if(!tt->active && tt->signal<0.5){ - tt->active=true; - if (stream != NULL) StringFormatter::send(stream, F(""), tt->data.snum); - } else if(tt->active && tt->signal>0.9){ - tt->active=false; - if (stream != NULL) StringFormatter::send(stream, F(""), tt->data.snum); - } - } // loop over all sensors - + if(!readingSensor->active && readingSensor->signal<0.5){ + readingSensor->active=true; + if (stream != NULL) StringFormatter::send(stream, F(""), readingSensor->data.snum); + } else if(readingSensor->active && readingSensor->signal>0.9){ + readingSensor->active=false; + if (stream != NULL) StringFormatter::send(stream, F(""), readingSensor->data.snum); + } + readingSensor=readingSensor->nextSensor; } // Sensor::checkAll /////////////////////////////////////////////////////////////////////////////// @@ -159,6 +160,7 @@ bool Sensor::remove(int n){ else pp->nextSensor=tt->nextSensor; + if (readingSensor==tt) readingSensor=tt->nextSensor; free(tt); return true; @@ -196,3 +198,4 @@ void Sensor::store(){ /////////////////////////////////////////////////////////////////////////////// Sensor *Sensor::firstSensor=NULL; +Sensor *Sensor::readingSensor=NULL; diff --git a/Sensors.h b/Sensors.h index 717da69..901d1e5 100644 --- a/Sensors.h +++ b/Sensors.h @@ -31,6 +31,7 @@ struct SensorData { struct Sensor{ static Sensor *firstSensor; + static Sensor *readingSensor; SensorData data; boolean active; float signal; From 9149bc0ee2b5586b02307535286f9247f0732fc3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Oct 2020 09:10:45 +0100 Subject: [PATCH 18/37] no float in Sensor no more --- Sensors.cpp | 30 +++++++++++++++++++++--------- Sensors.h | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Sensors.cpp b/Sensors.cpp index b9a9c9d..c914996 100644 --- a/Sensors.cpp +++ b/Sensors.cpp @@ -83,15 +83,27 @@ void Sensor::checkAll(Print *stream){ if (firstSensor == NULL) return; if (readingSensor == NULL) readingSensor=firstSensor; - readingSensor->signal=readingSensor->signal*(1.0-SENSOR_DECAY)+digitalRead(readingSensor->data.pin)*SENSOR_DECAY; - - if(!readingSensor->active && readingSensor->signal<0.5){ - readingSensor->active=true; - if (stream != NULL) StringFormatter::send(stream, F(""), readingSensor->data.snum); - } else if(readingSensor->active && readingSensor->signal>0.9){ - readingSensor->active=false; - if (stream != NULL) StringFormatter::send(stream, F(""), readingSensor->data.snum); + + bool sensorstate = digitalRead(readingSensor->data.pin); + + if (!sensorstate == readingSensor->active) { // active==true means sensorstate=0/false so sensor unchanged + // no change + if (readingSensor->latchdelay != 0) { + // enable if you want to debug contact jitter + //if (stream != NULL) StringFormatter::send(stream, F("JITTER %d %d\n"), + // readingSensor->latchdelay, readingSensor->data.snum); + readingSensor->latchdelay=0; // reset + } + } else if (readingSensor->latchdelay < 127) { // byte, max 255, good value unknown yet + // change but first increase anti-jitter counter + readingSensor->latchdelay++; + } else { + // make the change + readingSensor->active = !sensorstate; + readingSensor->latchdelay=0; // reset + if (stream != NULL) StringFormatter::send(stream, F("<%c %d>"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum); } + readingSensor=readingSensor->nextSensor; } // Sensor::checkAll @@ -131,7 +143,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp){ tt->data.pin=pin; tt->data.pullUp=(pullUp==0?LOW:HIGH); tt->active=false; - tt->signal=1; + tt->latchdelay=0; pinMode(pin,INPUT); // set mode to input digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor diff --git a/Sensors.h b/Sensors.h index 901d1e5..36e8157 100644 --- a/Sensors.h +++ b/Sensors.h @@ -34,7 +34,7 @@ struct Sensor{ static Sensor *readingSensor; SensorData data; boolean active; - float signal; + byte latchdelay; Sensor *nextSensor; static void load(); static void store(); From 6833d8278935b54fd9a076421a6bec462d314c17 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 26 Oct 2020 12:59:40 +0000 Subject: [PATCH 19/37] Buffer overflow resilience And diag output of replies --- RingStream.cpp | 25 +++++++++++++++++++++++-- RingStream.h | 4 ++++ WifiInboundHandler.cpp | 25 +++++++++++++++++++++---- WifiInboundHandler.h | 5 +++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index eee0753..cea17fb 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -28,11 +28,12 @@ RingStream::RingStream( const uint16_t len) _pos_read=0; _buffer[0]=0; _overflow=false; + _mark=0; } -size_t RingStream::write(uint8_t byte) { +size_t RingStream::write(uint8_t b) { if (_overflow) return 0; - _buffer[_pos_write] = byte; + _buffer[_pos_write] = b; ++_pos_write; if (_pos_write>=_len) _pos_write=0; if (_pos_write==_pos_read) { @@ -63,3 +64,23 @@ int RingStream::count() { } return counter; } + +int RingStream::freeSpace() { + if (_pos_read>_pos_write) return _pos_read-_pos_write-2; + else return _len - _pos_write + _pos_read-2; +} + + +void RingStream::mark() { + _mark=_pos_write; +} + +bool RingStream::commit() { + if (_overflow) { + _pos_write=_mark; + _overflow=false; + return false; // commit failed + } + write((uint8_t)0); + return true; // commit worked +} diff --git a/RingStream.h b/RingStream.h index 7a129e0..52caf9e 100644 --- a/RingStream.h +++ b/RingStream.h @@ -30,12 +30,16 @@ class RingStream : public Print { using Print::write; int read(); int count(); + int freeSpace(); + void mark(); + bool commit(); private: int _len; int _pos_write; int _pos_read; bool _overflow; + int _mark; byte * _buffer; }; diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 8a779ee..6aa8738 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -65,9 +65,12 @@ void WifiInboundHandler::loop1() { cmd[count]=0; if (Diag::WIFI) DIAG(F("%e\n"),cmd); + outboundRing->mark(); // remember start of outbound data outboundRing->print(clientId); CommandDistributor::parse(clientId,cmd,outboundRing); - outboundRing->write((byte)0); + // The commit call will either write the null byte at the end of the output, + // OR rollback to the mark because the commend generated more than fits rthe buffer + outboundRing->commit(); return; } } @@ -95,8 +98,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } if (ch=='>') { - if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),currentReplySize); - for (int i=0;iwrite(outboundRing->read()); + for (int i=0;iread(); + wifiStream->write(cout); + if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise + } outboundRing->read(); // drop the end marker clientPendingCIPSEND=-1; pendingCipsend=false; @@ -159,6 +165,12 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { break; } if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength); + if (inboundRing->freeSpace()<=(dataLength+1)) { + // This input would overflow the inbound ring, ignore it + loopState=IPD_IGNORE_DATA; + break; + } + inboundRing->mark(); inboundRing->print(runningClientId); // prefix inbound with client id loopState=IPD_DATA; break; @@ -170,11 +182,16 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { inboundRing->write(ch); dataLength--; if (dataLength == 0) { - inboundRing->write((byte)0); + inboundRing->commit(); loopState = ANYTHING; } break; + case IPD_IGNORE_DATA: // ignoring data that would not fit in inbound ring + dataLength--; + if (dataLength == 0) loopState = ANYTHING; + break; + case GOT_CLIENT_ID: // got x before CLOSE or CONNECTED loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND; break; diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index fa41aa4..e947bdb 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -32,6 +32,7 @@ class WifiInboundHandler { IPD5, // got +IPD,c IPD6_LENGTH, // got +IPD,c, reading length IPD_DATA, // got +IPD,c,ll,: collecting data + IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED @@ -44,8 +45,8 @@ class WifiInboundHandler { INBOUND_STATE loop2(); Stream * wifiStream; - static const int INBOUND_RING = 200; - static const int OUTBOUND_RING = 1024; + static const int INBOUND_RING = 512; + static const int OUTBOUND_RING = 2048; RingStream * inboundRing; RingStream * outboundRing; From 8ff947f895dbe25fc5cffe47fdc8c60335b2aec2 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 26 Oct 2020 13:31:51 +0000 Subject: [PATCH 20/37] Passing outbound Ring to Withrottle This will allow Withrottle to send to other clients and broadcast messages. --- CommandDistributor.cpp | 2 +- CommandDistributor.h | 3 ++- WiThrottle.cpp | 12 ++++++++++-- WiThrottle.h | 5 +++-- WifiInboundHandler.cpp | 2 ++ WifiInboundHandler.h | 1 + WifiInterface.cpp | 3 +-- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4d13f40..4496692 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -22,7 +22,7 @@ DCCEXParser * CommandDistributor::parser=0; -void CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) { +void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) { if (buffer[0] == '<') { if (!parser) parser = new DCCEXParser(); parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async diff --git a/CommandDistributor.h b/CommandDistributor.h index 2bec81b..155ada0 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -19,11 +19,12 @@ #ifndef CommandDistributor_h #define CommandDistributor_h #include "DCCEXParser.h" +#include "RingStream.h" class CommandDistributor { public : - static void parse(byte clientId,byte* buffer, Print * streamer); + static void parse(byte clientId,byte* buffer, RingStream * streamer); private: static DCCEXParser * parser; }; diff --git a/WiThrottle.cpp b/WiThrottle.cpp index ac503a0..95ea07e 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -99,7 +99,7 @@ WiThrottle::~WiThrottle() { } } -void WiThrottle::parse(Print & stream, byte * cmdx) { +void WiThrottle::parse(RingStream & stream, byte * cmdx) { byte * cmd=cmdx; @@ -334,10 +334,18 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) { return WiTSpeed + 1; //offset others by 1 } -void WiThrottle::loop() { +void WiThrottle::loop(RingStream & stream) { // for each WiThrottle, check the heartbeat for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) wt->checkHeartbeat(); + + // TODO... any broadcasts to be done + /* MUST follow this model in this loop. + * stream->mark(); + * send 1 digit client id, and any data + * stream->commit() + */ + } void WiThrottle::checkHeartbeat() { diff --git a/WiThrottle.h b/WiThrottle.h index c1b5bba..fe06cfe 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -19,6 +19,7 @@ #ifndef WiThrottle_h #define WiThrottle_h +#include "RingStream.h" struct MYLOCO { char throttle; //indicates which throttle letter on client, often '0','1' or '2' @@ -27,8 +28,8 @@ struct MYLOCO { class WiThrottle { public: - static void loop(); - void parse(Print & stream, byte * cmd); + static void loop(RingStream & stream); + void parse(RingStream & stream, byte * cmd); static WiThrottle* getThrottle( int wifiClient); static bool annotateLeftRight; private: diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 6aa8738..a13e789 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -31,6 +31,8 @@ void WifiInboundHandler::loop1() { // First handle all inbound traffic events because they will block the sending if (loop2()!=INBOUND_IDLE) return; + WiThrottle::loop(outboundRing); + // if nothing is already CIPSEND pending, we can CIPSEND one reply if (clientPendingCIPSEND<0) { int next=outboundRing->read(); diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index e947bdb..953b08d 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -2,6 +2,7 @@ #define WifiInboundHandler_h #include "RingStream.h" +#include "WiThrottle.h" #include "DIAG.h" class WifiInboundHandler { diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c751cab..74018b9 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -22,7 +22,7 @@ #include #include "DIAG.h" #include "StringFormatter.h" -#include "WiThrottle.h" + #include "WifiInboundHandler.h" const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; @@ -295,7 +295,6 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor void WifiInterface::loop() { if (connected) { - WiThrottle::loop(); WifiInboundHandler::loop(); } } From b7fc055953adca899a3d4470b87785a1dc7d0511 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 26 Oct 2020 13:58:25 +0000 Subject: [PATCH 21/37] Passing Strteamer correctly Previous method copied entire buffer!!!! --- CommandDistributor.cpp | 2 +- WiThrottle.cpp | 9 +++++---- WiThrottle.h | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4496692..de4e8bf 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -27,5 +27,5 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream if (!parser) parser = new DCCEXParser(); parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async } - else WiThrottle::getThrottle(clientId)->parse(*streamer, buffer); + else WiThrottle::getThrottle(clientId)->parse(streamer, buffer); } diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 95ea07e..61f7f92 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -99,7 +99,7 @@ WiThrottle::~WiThrottle() { } } -void WiThrottle::parse(RingStream & stream, byte * cmdx) { +void WiThrottle::parse(RingStream * stream, byte * cmdx) { byte * cmd=cmdx; @@ -205,7 +205,7 @@ int WiThrottle::getLocoId(byte * cmd) { if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos return getInt(cmd+1); } -void WiThrottle::multithrottle(Print & stream, byte * cmd){ +void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ char throttleChar=cmd[1]; int locoid=getLocoId(cmd+3); // -1 for * byte * aval=cmd; @@ -256,7 +256,7 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){ } } -void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int cab){ +void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){ // Note cab=-1 for all cabs in the consist called throttleChar. // DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab); switch (aval[0]) { @@ -334,12 +334,13 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) { return WiTSpeed + 1; //offset others by 1 } -void WiThrottle::loop(RingStream & stream) { +void WiThrottle::loop(RingStream * stream) { // for each WiThrottle, check the heartbeat for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) wt->checkHeartbeat(); // TODO... any broadcasts to be done + (void)stream; /* MUST follow this model in this loop. * stream->mark(); * send 1 digit client id, and any data diff --git a/WiThrottle.h b/WiThrottle.h index fe06cfe..161020d 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -28,8 +28,8 @@ struct MYLOCO { class WiThrottle { public: - static void loop(RingStream & stream); - void parse(RingStream & stream, byte * cmd); + static void loop(RingStream * stream); + void parse(RingStream * stream, byte * cmd); static WiThrottle* getThrottle( int wifiClient); static bool annotateLeftRight; private: @@ -57,9 +57,9 @@ class WiThrottle { bool lastPowerState; // last power state sent to this client int DCCToWiTSpeed(int DCCSpeed); int WiTToDCCSpeed(int WiTSpeed); - void multithrottle(Print & stream, byte * cmd); - void locoAction(Print & stream, byte* aval, char throttleChar, int cab); - void accessory(Print & stream, byte* cmd); + void multithrottle(RingStream * stream, byte * cmd); + void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab); + void accessory(RingStream *, byte* cmd); void checkHeartbeat(); }; #endif From 369a75f958ad1e428e7c0ce01922587fe7a52a25 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Mon, 26 Oct 2020 12:57:37 -0400 Subject: [PATCH 22/37] Fix return values for outputs and sensors ( and responses --- DCCEXParser.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 712313c..027e6c9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -436,10 +436,17 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) case 3: // Output::create(p[0], p[1], p[2], 1); + if (!Output::create(p[0], p[1], p[2], 1)) + return false; + StringFormatter::send(stream, F("")); return true; case 1: // return Output::remove(p[0]); + if (!Output::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; case 0: // { @@ -548,11 +555,18 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) { case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) Sensor::create(p[0], p[1], p[2]); + if (!Sensor::create(p[0], p[1], p[2])) + return false; + StringFormatter::send(stream, F("")); return true; case 1: // S id> remove sensor if (Sensor::remove(p[0])) return true; + if (!Sensor::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; break; case 0: // lit sensor states From babdad06cab2ae3148d9c7fbcb08dadbba55fdf4 Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Mon, 26 Oct 2020 13:28:47 -0400 Subject: [PATCH 23/37] Remove commands that were called twice --- DCCEXParser.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 027e6c9..57ed9e7 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -435,14 +435,12 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) return true; case 3: // - Output::create(p[0], p[1], p[2], 1); if (!Output::create(p[0], p[1], p[2], 1)) return false; StringFormatter::send(stream, F("")); return true; case 1: // - return Output::remove(p[0]); if (!Output::remove(p[0])) return false; StringFormatter::send(stream, F("")); @@ -554,15 +552,12 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) switch (params) { case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) - Sensor::create(p[0], p[1], p[2]); if (!Sensor::create(p[0], p[1], p[2])) return false; StringFormatter::send(stream, F("")); return true; case 1: // S id> remove sensor - if (Sensor::remove(p[0])) - return true; if (!Sensor::remove(p[0])) return false; StringFormatter::send(stream, F("")); From 8101e75daec5025852254afa5e93106a4bac667e Mon Sep 17 00:00:00 2001 From: FrightRisk <37218136+FrightRisk@users.noreply.github.com> Date: Mon, 26 Oct 2020 14:00:25 -0400 Subject: [PATCH 24/37] Remove unneccessary break command --- DCCEXParser.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 57ed9e7..b1eb631 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -562,7 +562,6 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) return false; StringFormatter::send(stream, F("")); return true; - break; case 0: // lit sensor states for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) From 019001675e6637b60f4a7e59cd3c9a6e692b81a2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 27 Oct 2020 07:24:48 +0100 Subject: [PATCH 25/37] return false if parseS does not find any sensors --- DCCEXParser.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b1eb631..9432a4f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -436,7 +436,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[]) case 3: // if (!Output::create(p[0], p[1], p[2], 1)) - return false; + return false; StringFormatter::send(stream, F("")); return true; @@ -554,7 +554,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) if (!Sensor::create(p[0], p[1], p[2])) return false; - StringFormatter::send(stream, F("")); + StringFormatter::send(stream, F("")); return true; case 1: // S id> remove sensor @@ -564,6 +564,8 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[]) return true; case 0: // lit sensor states + if (Sensor::firstSensor == NULL) + return false; for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) { StringFormatter::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); From 59a1a8eb1b819546c265f27752446ca08de5bedc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 27 Oct 2020 07:33:04 +0100 Subject: [PATCH 26/37] turn off tcp server to reset connections --- WifiInterface.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c751cab..b718b4b 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -233,6 +233,8 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH } } + StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1) + checkForOK(10000, OK_SEARCH, true); // ignore result in case it already was off StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections if (!checkForOK(10000, OK_SEARCH, true)) return false; From 6c0b5d82be8ebcd68e14e08be2fbc98b56b74ef5 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 27 Oct 2020 10:38:30 +0000 Subject: [PATCH 27/37] Ring count improvements Count is stashed at start of message so can be got without counting. Commit discards empty replies automatically. --- RingStream.cpp | 51 ++++++++++++++++++++++++++---------------- RingStream.h | 3 ++- WifiInboundHandler.cpp | 41 ++++++++++++++------------------- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index cea17fb..911bb80 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -29,58 +29,71 @@ RingStream::RingStream( const uint16_t len) _buffer[0]=0; _overflow=false; _mark=0; + _count=0; } size_t RingStream::write(uint8_t b) { if (_overflow) return 0; _buffer[_pos_write] = b; ++_pos_write; - if (_pos_write>=_len) _pos_write=0; + if (_pos_write==_len) _pos_write=0; if (_pos_write==_pos_read) { _overflow=true; - DIAG(F("\nRingStream(%d) OVERFLOW %d %d \n"),_len, _pos_write, _pos_read); return 0; } + _count++; return 1; } int RingStream::read() { - if (_pos_read==_pos_write) return -1; + if ((_pos_read==_pos_write) && !_overflow) return -1; // empty byte b=_buffer[_pos_read]; _pos_read++; - if (_pos_read>=_len) _pos_read=0; + if (_pos_read==_len) _pos_read=0; _overflow=false; return b; } int RingStream::count() { - int peek=_pos_read; - int counter=0; - while(_buffer[peek]) { - counter++; - peek++; - if (peek >= _len) peek=0; - } - return counter; + return (read()<<8) | read(); } int RingStream::freeSpace() { - if (_pos_read>_pos_write) return _pos_read-_pos_write-2; - else return _len - _pos_write + _pos_read-2; + // allow space for client flag and length bytes + if (_pos_read>_pos_write) return _pos_read-_pos_write-3; + else return _len - _pos_write + _pos_read-3; } -void RingStream::mark() { +// mark start of message with client id (0...9) +void RingStream::mark(uint8_t b) { _mark=_pos_write; + write(b); // client id + write((uint8_t)0); // count MSB placemarker + write((uint8_t)0); // count LSB placemarker + _count=0; } bool RingStream::commit() { if (_overflow) { - _pos_write=_mark; - _overflow=false; - return false; // commit failed + DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count); + // just throw it away + _pos_write=_mark; + _overflow=false; + return false; // commit failed } - write((uint8_t)0); + if (_count==0) { + // ignore empty response + _pos_write=_mark; + return true; // true=commit ok + } + // Go back to the _mark and inject the count 1 byte later + _mark++; + if (_mark==_len) _mark=0; + _buffer[_mark]=highByte(_count); + _mark++; + if (_mark==_len) _mark=0; + _buffer[_mark]=lowByte(_count); return true; // commit worked } diff --git a/RingStream.h b/RingStream.h index 52caf9e..b32e062 100644 --- a/RingStream.h +++ b/RingStream.h @@ -31,7 +31,7 @@ class RingStream : public Print { int read(); int count(); int freeSpace(); - void mark(); + void mark(uint8_t b); bool commit(); private: @@ -40,6 +40,7 @@ class RingStream : public Print { int _pos_read; bool _overflow; int _mark; + int _count; byte * _buffer; }; diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index a13e789..81cb3b7 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -35,18 +35,13 @@ void WifiInboundHandler::loop1() { // if nothing is already CIPSEND pending, we can CIPSEND one reply if (clientPendingCIPSEND<0) { - int next=outboundRing->read(); - if (next>=0) { + clientPendingCIPSEND=outboundRing->read(); + if (clientPendingCIPSEND>=0) { currentReplySize=outboundRing->count(); - if (currentReplySize==0) { - outboundRing->read(); // drop end marker - } - else { - clientPendingCIPSEND=next-'0'; // convert back to int - pendingCipsend=true; - } + pendingCipsend=true; } - } + } + if (pendingCipsend) { if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); @@ -57,21 +52,19 @@ void WifiInboundHandler::loop1() { // if something waiting to execute, we can call it - int next=inboundRing->read(); - if (next>0) { - int clientId=next-'0'; //convert char to int + int clientId=inboundRing->read(); + if (clientId>=0) { int count=inboundRing->count(); - if (Diag::WIFI) DIAG(F("\nExec waiting %d %d:"),clientId,count); + if (Diag::WIFI) DIAG(F("\nWifi EXEC: %d %d:"),clientId,count); byte cmd[count+1]; for (int i=0;iread(); cmd[count]=0; if (Diag::WIFI) DIAG(F("%e\n"),cmd); - outboundRing->mark(); // remember start of outbound data - outboundRing->print(clientId); + outboundRing->mark(clientId); // remember start of outbound data CommandDistributor::parse(clientId,cmd,outboundRing); - // The commit call will either write the null byte at the end of the output, - // OR rollback to the mark because the commend generated more than fits rthe buffer + // The commit call will either write the lenbgth bytes + // OR rollback to the mark because the reply is empty or commend generated more than fits the buffer outboundRing->commit(); return; } @@ -92,7 +85,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } switch (loopState) { - case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED + case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR if (ch == '+') { loopState = IPD; @@ -100,12 +93,12 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } if (ch=='>') { + if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize); for (int i=0;iread(); wifiStream->write(cout); if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise } - outboundRing->read(); // drop the end marker clientPendingCIPSEND=-1; pendingCipsend=false; loopState=SKIPTOEND; @@ -170,10 +163,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { if (inboundRing->freeSpace()<=(dataLength+1)) { // This input would overflow the inbound ring, ignore it loopState=IPD_IGNORE_DATA; + if (Diag::WIFI) DIAG(F("\nWifi OVERFLOW IGNORING:")); break; } - inboundRing->mark(); - inboundRing->print(runningClientId); // prefix inbound with client id + inboundRing->mark(runningClientId); loopState=IPD_DATA; break; } @@ -181,10 +174,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { break; case IPD_DATA: // reading data - inboundRing->write(ch); + inboundRing->write(ch); dataLength--; if (dataLength == 0) { - inboundRing->commit(); + inboundRing->commit(); loopState = ANYTHING; } break; From 138851f45f5edda7df1914a330313b18a9d56a7a Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 27 Oct 2020 11:04:51 +0000 Subject: [PATCH 28/37] Cleaner purge on ERROR --- WifiInboundHandler.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 81cb3b7..00a682f 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -85,7 +85,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } switch (loopState) { - case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR + case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR, SEND OK if (ch == '+') { loopState = IPD; @@ -109,6 +109,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { loopState=SKIPTOEND; break; } + + if (ch=='S') { // SEND OK probably + loopState=SKIPTOEND; + break; + } if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND pendingCipsend=(clientPendingCIPSEND>=0); @@ -121,6 +126,18 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { loopState=GOT_CLIENT_ID; break; } + + if (ch=='E') { // ERROR + if (pendingCipsend) { + // A CIPSEND was errored... just toss it away + if (Diag::WIFI) DIAG(F("Wifi: drop previous CIPSEND\n")); + for (int i=0;i<=currentReplySize;i++) outboundRing->read(); + clientPendingCIPSEND=-1; + pendingCipsend=false; + } + loopState=SKIPTOEND; + break; + } break; @@ -200,7 +217,9 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { // CLOSE if (runningClientId==clientPendingCIPSEND) { // clear the outbound for this client - for (int i=0;i<=currentReplySize;i++) outboundRing->read(); + for (int i=0;i<=currentReplySize;i++) outboundRing->read(); + clientPendingCIPSEND=-1; + pendingCipsend=false; } } loopState=SKIPTOEND; From a65ca0b704cffb3564fbf17aabb82d623a37c761 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 27 Oct 2020 12:33:01 +0000 Subject: [PATCH 29/37] Cleaner purge after error or connect fail --- WifiInboundHandler.cpp | 38 +++++++++++++++++--------------------- WifiInboundHandler.h | 1 + 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 00a682f..2463921 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -127,13 +127,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { break; } - if (ch=='E') { // ERROR - if (pendingCipsend) { + if (ch=='E' || ch=='l') { // ERROR or "link is not valid" + if (clientPendingCIPSEND>=0) { // A CIPSEND was errored... just toss it away - if (Diag::WIFI) DIAG(F("Wifi: drop previous CIPSEND\n")); - for (int i=0;i<=currentReplySize;i++) outboundRing->read(); - clientPendingCIPSEND=-1; - pendingCipsend=false; + purgeCurrentCIPSEND(); } loopState=SKIPTOEND; break; @@ -208,22 +205,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND; break; - case GOT_CLIENT_ID2: // got "x," before CLOSE or CONNECTED - loopState=(ch=='C') ? GOT_CLIENT_ID3: SKIPTOEND; + case GOT_CLIENT_ID2: // got "x," + if (ch=='C') { + // got "x C" before CLOSE or CONNECTED, or CONNECT FAILED + if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND(); + } + loopState=SKIPTOEND; break; - - case GOT_CLIENT_ID3: // got "x C" before CLOSE or CONNECTED (which is ignored) - if(ch=='L') { - // CLOSE - if (runningClientId==clientPendingCIPSEND) { - // clear the outbound for this client - for (int i=0;i<=currentReplySize;i++) outboundRing->read(); - clientPendingCIPSEND=-1; - pendingCipsend=false; - } - } - loopState=SKIPTOEND; - break; case SKIPTOEND: // skipping for /n if (ch=='\n') loopState=ANYTHING; @@ -232,3 +220,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { } // available return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY; } + +void WifiInboundHandler::purgeCurrentCIPSEND() { + // A CIPSEND was sent but errored... or the client closed just toss it away + if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d\n"),clientPendingCIPSEND,currentReplySize); + for (int i=0;i<=currentReplySize;i++) outboundRing->read(); + pendingCipsend=false; + clientPendingCIPSEND=-1; +} diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index 953b08d..1979565 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -44,6 +44,7 @@ class WifiInboundHandler { WifiInboundHandler(Stream * ESStream); void loop1(); INBOUND_STATE loop2(); + void purgeCurrentCIPSEND(); Stream * wifiStream; static const int INBOUND_RING = 512; From 96fba12d59ccdebf531a8ff602e668e2b17d637f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 27 Oct 2020 12:50:01 +0000 Subject: [PATCH 30/37] ONLY 1 Call to WifiInboundHandler::setup! Or it wastes 1.5kb each time! --- WifiInterface.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 74018b9..9e7e6ed 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -90,6 +90,11 @@ bool WifiInterface::setup(long serial_link_speed, } #endif +DCCEXParser::setAtCommandCallback(ATCommand); + +// CAUTION... ONLY CALL THIS ONCE +WifiInboundHandler::setup(wifiStream); + return wifiUp; } @@ -109,8 +114,6 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid checkForOK(200, OK_SEARCH, true); } - DCCEXParser::setAtCommandCallback(ATCommand); - WifiInboundHandler::setup(wifiStream); DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); return connected; From c6cff3b4bdbce801af655d4156718f224fdfb0d4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 27 Oct 2020 21:50:46 +0100 Subject: [PATCH 31/37] wifi setup tristate --- WifiInterface.cpp | 47 ++++++++++++++++++++++++++++------------------- WifiInterface.h | 6 ++++-- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index b718b4b..65b5047 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -57,7 +57,7 @@ bool WifiInterface::setup(long serial_link_speed, const __FlashStringHelper *hostname, const int port) { - bool wifiUp = false; + wifiSerialState wifiUp = WIFI_NOAT; #if NUM_SERIAL == 0 // no warning about unused parameters. @@ -75,7 +75,7 @@ bool WifiInterface::setup(long serial_link_speed, // Other serials are tried, depending on hardware. #if NUM_SERIAL > 1 - if (!wifiUp) + if (wifiUp == WIFI_NOAT) { Serial2.begin(serial_link_speed); wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port); @@ -83,18 +83,22 @@ bool WifiInterface::setup(long serial_link_speed, #endif #if NUM_SERIAL > 2 - if (!wifiUp) + if (wifiUp == WIFI_NOAT) { Serial3.begin(serial_link_speed); wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port); } #endif - -return wifiUp; + if (wifiUp == WIFI_CONNECTED) + connected = true; + else + connected = false; + return connected; } -bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port) { +wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, + const __FlashStringHelper* hostname, int port) { + wifiSerialState wifiState; static uint8_t ntry = 0; ntry++; @@ -102,9 +106,14 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry); - connected = setup2( SSid, password, hostname, port); + wifiState = setup2( SSid, password, hostname, port); + + if (wifiState == WIFI_NOAT) { + DIAG(F("\n++ Wifi Setup NO AT ++\n")); + return wifiState; + } - if (connected) { + if (wifiState == WIFI_CONNECTED) { StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo checkForOK(200, OK_SEARCH, true); } @@ -112,12 +121,12 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid DCCEXParser::setAtCommandCallback(ATCommand); WifiInboundHandler::setup(wifiStream); - DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); - return connected; + DIAG(F("\n++ Wifi Setup %S ++\n"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED")); + return wifiState; } -bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port) { +wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password, + const __FlashStringHelper* hostname, int port) { bool ipOK = false; bool oldCmd = false; @@ -129,12 +138,12 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH if (checkForOK(200,IPD_SEARCH, true)) { DIAG(F("\nPreconfigured Wifi already running with data waiting\n")); // loopstate=4; // carry on from correct place... or not as the case may be - return true; + return WIFI_CONNECTED; } StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT? if(!checkForOK(200, OK_SEARCH, true)) - return false; // No AT compatible WiFi module here + return WIFI_NOAT; // No AT compatible WiFi module here StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console @@ -237,16 +246,16 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH checkForOK(10000, OK_SEARCH, true); // ignore result in case it already was off StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections - if (!checkForOK(10000, OK_SEARCH, true)) return false; + if (!checkForOK(10000, OK_SEARCH, true)) return WIFI_DISCONNECTED; StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port - if (!checkForOK(10000, OK_SEARCH, true)) return false; + if (!checkForOK(10000, OK_SEARCH, true)) return WIFI_DISCONNECTED; StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG - if (!checkForOK(10000, OK_SEARCH, true, false)) return false; + if (!checkForOK(10000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED; DIAG(F("\nPORT=%d\n"),port); - return true; + return WIFI_CONNECTED; } diff --git a/WifiInterface.h b/WifiInterface.h index b2bdd20..033cbc9 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -24,6 +24,8 @@ #include #include +enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED }; + class WifiInterface { @@ -37,11 +39,11 @@ public: static void ATCommand(const byte *command); private: - static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + static wifiSerialState setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, const __FlashStringHelper *hostname, int port); static Stream *wifiStream; static DCCEXParser parser; - static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + static wifiSerialState setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, const __FlashStringHelper *hostname, int port); static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true); static bool connected; From 7537f4c3d64864ee926074be9daf1359fe0506d7 Mon Sep 17 00:00:00 2001 From: Fred Date: Wed, 28 Oct 2020 14:14:11 -0400 Subject: [PATCH 32/37] Update CommandStation-EX.ino --- CommandStation-EX.ino | 87 ++++++++----------------------------------- 1 file changed, 15 insertions(+), 72 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 41a4c50..9749fea 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,19 +12,6 @@ #include "config.h" #include "DCCEX.h" - -//////////////////////////////////////////////////////////////// -// -// Enables an I2C 2x24 or 4x24 LCD Screen -#ifdef ENABLE_LCD -bool lcdEnabled = false; -#if defined(LIB_TYPE_PCF8574) -LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); -#elif defined(LIB_TYPE_I2C) -LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); -#endif -#endif - // 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. @@ -32,70 +19,23 @@ DCCEXParser serialParser; void setup() { - -//////////////////////////////////////////// -// -// More display stuff. Need to put this in a .h file and make -// it a class -#ifdef ENABLE_LCD - Wire.begin(); - // Check that we can find the LCD by its address before attempting to use it. - Wire.beginTransmission(LCD_ADDRESS); - if (Wire.endTransmission() == 0) - { - lcdEnabled = true; - lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); - lcdDisplay.setBacklight(128); - lcdDisplay.clear(); - lcdDisplay.setCursor(0, 0); - lcdDisplay.print("DCC++ EX v"); - lcdDisplay.print(VERSION); - lcdDisplay.setCursor(0, 1); -//#if COMM_INTERFACE >= 1 -// lcdDisplay.print("IP: PENDING"); -//#else - lcdDisplay.print("SERIAL: READY 1"); -//#endif -#if LCD_LINES > 2 - lcdDisplay.setCursor(0, 3); - lcdDisplay.print("TRACK POWER: OFF"); -#endif - } -#endif - // The main sketch has responsibilities during setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); + DIAG(F("DCC++ EX v%S"),F(VERSION)); + + CONDITIONAL_LCD_START { + // This block is ignored if LCD not in use + LCD(0,F("DCC++ EX v%S"),F(VERSION)); + LCD(1,F("Starting")); + } // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi -// NOTE: References to Serial1 are for the serial port used to connect -// your wifi chip/shield. -#ifdef WIFI_ON - bool wifiUp = false; - const __FlashStringHelper *wifiESSID = F(WIFI_SSID); - const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); - const __FlashStringHelper *dccex = F(WIFI_HOSTNAME); - const uint16_t port = IP_PORT; - - 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 // >2 -#endif // >1 +#if WIFI_ON + WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT); #endif // WIFI_ON // Responsibility 3: Start the DCC engine. @@ -108,7 +48,8 @@ void setup() // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(MOTOR_SHIELD_TYPE); + LCD(1,F("Ready")); } void loop() @@ -123,10 +64,12 @@ void loop() serialParser.loop(Serial); // Responsibility 3: Optionally handle any incoming WiFi traffic -#ifdef WIFI_ON +#if WIFI_ON WifiInterface::loop(); #endif + LCDDisplay::loop(); // ignored if LCD not in use + // 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 @@ -135,7 +78,7 @@ void loop() if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; - DIAG(F("\nFree RAM=%d\n"), ramLowWatermark); + LCD(2,F("Free RAM=%5db"), ramLowWatermark); } #endif } From 8ed8db11272dceb83a19c890e7a4d969d094210b Mon Sep 17 00:00:00 2001 From: Fred Date: Wed, 28 Oct 2020 14:22:34 -0400 Subject: [PATCH 33/37] Update DCC.h --- DCC.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/DCC.h b/DCC.h index 529e326..b447baf 100644 --- a/DCC.h +++ b/DCC.h @@ -72,6 +72,7 @@ public: static void setFunction(int cab, byte fByte, byte eByte); static void setFn(int cab, byte functionNumber, bool on); static int changeFn(int cab, byte functionNumber, bool pressed); + static int getFn(int cab, byte functionNumber); static void updateGroupflags(byte &flags, int functionNumber); static void setAccessory(int aAdd, byte aNum, bool activate); static bool writeTextPacket(byte *b, int nBytes); @@ -162,16 +163,5 @@ private: #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif -#ifdef ENABLE_LCD -#include -#if defined(LIB_TYPE_PCF8574) -#include -extern LiquidCrystal_PCF8574 lcdDisplay; -#elif defined(LIB_TYPE_I2C) -#include -extern LiquidCrystal_I2C lcdDisplay; -#endif -extern bool lcdEnabled; -#endif #endif From 177bf202a8784aacd2a2334ece80c71883521131 Mon Sep 17 00:00:00 2001 From: Fred Date: Wed, 28 Oct 2020 14:27:16 -0400 Subject: [PATCH 34/37] Update Outputs.cpp remove extra blank line --- Outputs.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Outputs.cpp b/Outputs.cpp index d72f744..84290fe 100644 --- a/Outputs.cpp +++ b/Outputs.cpp @@ -90,7 +90,6 @@ void Output::activate(int s){ digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) if(num>0) EEPROM.put(num,data.oStatus); - } /////////////////////////////////////////////////////////////////////////////// From 27f3842ef576aed0c24258541478a10f61806b6c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 28 Oct 2020 20:32:20 +0100 Subject: [PATCH 35/37] do second try to set AP ESSID and PASSWORD if necessary --- WifiInterface.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index be17b02..77294ae 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -223,8 +223,9 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH if (oldCmd) { while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE - StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); - checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok + int i=0; + do StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); + while (i++<2 && !checkForOK(16000, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok } else { From df9b7813a9bae1dcbfe984ef02cbd789b3d8f9c6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 28 Oct 2020 22:00:07 +0100 Subject: [PATCH 36/37] include ctype and add license notice --- EEStore.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/EEStore.cpp b/EEStore.cpp index a3d2d55..e1131ac 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -1,3 +1,24 @@ +/* + * © 2013-2016 Gregg E. Berman + * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. + * + * 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 . + */ +#include // isascii in dump for diagnostics #include "EEStore.h" #include "Turnouts.h" #include "Sensors.h" From 50e85d0b79b8da612bf57f15a09121c9fb710daa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 28 Oct 2020 23:19:55 +0100 Subject: [PATCH 37/37] better isprint instead --- EEStore.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EEStore.cpp b/EEStore.cpp index e1131ac..14ed925 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -18,7 +18,6 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ -#include // isascii in dump for diagnostics #include "EEStore.h" #include "Turnouts.h" #include "Sensors.h" @@ -98,7 +97,7 @@ void EEStore::dump(int num) { DIAG(F("\nAddr 0x char\n")); for (int n=0 ; n