mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-23 16:16:13 +01:00
Merge branch 'master' of https://github.com/mstevetodd/CommandStation-EX
This commit is contained in:
commit
c53dea018f
|
@ -1,54 +1,31 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "WiThrottle.h"
|
#include "WiThrottle.h"
|
||||||
|
|
||||||
DCCEXParser * CommandDistributor::parser=0;
|
DCCEXParser * CommandDistributor::parser=0;
|
||||||
|
|
||||||
bool CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) {
|
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||||
|
if (buffer[0] == '<') {
|
||||||
|
|
||||||
// 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] == '<') {
|
|
||||||
if (!parser) parser = new DCCEXParser();
|
if (!parser) parser = new DCCEXParser();
|
||||||
parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
|
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);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
#ifndef CommandDistributor_h
|
#ifndef CommandDistributor_h
|
||||||
#define CommandDistributor_h
|
#define CommandDistributor_h
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
|
#include "RingStream.h"
|
||||||
typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd);
|
|
||||||
|
|
||||||
class CommandDistributor {
|
class CommandDistributor {
|
||||||
|
|
||||||
public :
|
public :
|
||||||
static void setHTTPCallback(HTTP_CALLBACK callback);
|
static void parse(byte clientId,byte* buffer, RingStream * streamer);
|
||||||
static bool parse(byte clientId,byte* buffer, Print * streamer);
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static HTTP_CALLBACK httpCallback;
|
|
||||||
static bool isHTTP(byte * buffer);
|
|
||||||
static DCCEXParser * parser;
|
static DCCEXParser * parser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,18 +12,6 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "DCCEX.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,
|
// Create a serial command parser for the USB connection,
|
||||||
// This supports JMRI or manual diagnostics and commands
|
// This supports JMRI or manual diagnostics and commands
|
||||||
// to be issued from the USB serial console.
|
// to be issued from the USB serial console.
|
||||||
|
@ -31,45 +19,22 @@ DCCEXParser serialParser;
|
||||||
|
|
||||||
void setup()
|
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()
|
// The main sketch has responsibilities during setup()
|
||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics
|
// Responsibility 1: Start the usb connection for diagnostics
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||||
Serial.begin(115200);
|
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
|
// 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);
|
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT);
|
||||||
#endif // WIFI_ON
|
#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
|
// 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
|
// 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()
|
void loop()
|
||||||
|
@ -101,7 +67,9 @@ void loop()
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
WifiInterface::loop();
|
WifiInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
LCDDisplay::loop(); // ignored if LCD not in use
|
||||||
|
|
||||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
// Optionally report any decrease in memory (will automatically trigger on first call)
|
||||||
#if ENABLE_FREE_MEM_WARNING
|
#if ENABLE_FREE_MEM_WARNING
|
||||||
static int ramLowWatermark = 32767; // replaced on first loop
|
static int ramLowWatermark = 32767; // replaced on first loop
|
||||||
|
@ -110,7 +78,7 @@ void loop()
|
||||||
if (freeNow < ramLowWatermark)
|
if (freeNow < ramLowWatermark)
|
||||||
{
|
{
|
||||||
ramLowWatermark = freeNow;
|
ramLowWatermark = freeNow;
|
||||||
DIAG(F("\nFree RAM=%d\n"), ramLowWatermark);
|
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
15
DCC.cpp
15
DCC.cpp
|
@ -155,6 +155,15 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
||||||
return funcstate;
|
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<<functionNumber);
|
||||||
|
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the group flag to say we have touched the particular group.
|
// Set the group flag to say we have touched the particular group.
|
||||||
// A group will be reminded only if it has been touched.
|
// A group will be reminded only if it has been touched.
|
||||||
void DCC::updateGroupflags(byte & flags, int functionNumber) {
|
void DCC::updateGroupflags(byte & flags, int functionNumber) {
|
||||||
|
@ -456,15 +465,15 @@ bool DCC::issueReminder(int reg) {
|
||||||
break;
|
break;
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
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;
|
break;
|
||||||
case 2: // remind function group 2 F5-F8
|
case 2: // remind function group 2 F5-F8
|
||||||
if (flags & FN_GROUP_2)
|
if (flags & FN_GROUP_2)
|
||||||
setFunctionInternal(loco,0, 176 + ((functions>>5)& 0x0F));
|
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
||||||
break;
|
break;
|
||||||
case 3: // remind function group 3 F9-F12
|
case 3: // remind function group 3 F9-F12
|
||||||
if (flags & FN_GROUP_3)
|
if (flags & FN_GROUP_3)
|
||||||
setFunctionInternal(loco,0, 160 + ((functions>>9)& 0x0F));
|
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
||||||
break;
|
break;
|
||||||
case 4: // remind function group 4 F13-F20
|
case 4: // remind function group 4 F13-F20
|
||||||
if (flags & FN_GROUP_4)
|
if (flags & FN_GROUP_4)
|
||||||
|
|
12
DCC.h
12
DCC.h
|
@ -72,6 +72,7 @@ public:
|
||||||
static void setFunction(int cab, byte fByte, byte eByte);
|
static void setFunction(int cab, byte fByte, byte eByte);
|
||||||
static void setFn(int cab, byte functionNumber, bool on);
|
static void setFn(int cab, byte functionNumber, bool on);
|
||||||
static int changeFn(int cab, byte functionNumber, bool pressed);
|
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 updateGroupflags(byte &flags, int functionNumber);
|
||||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||||
static bool writeTextPacket(byte *b, int nBytes);
|
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
|
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLE_LCD
|
|
||||||
#include <Wire.h>
|
|
||||||
#if defined(LIB_TYPE_PCF8574)
|
|
||||||
#include <LiquidCrystal_PCF8574.h>
|
|
||||||
extern LiquidCrystal_PCF8574 lcdDisplay;
|
|
||||||
#elif defined(LIB_TYPE_I2C)
|
|
||||||
#include <LiquidCrystal_I2C.h>
|
|
||||||
extern LiquidCrystal_I2C lcdDisplay;
|
|
||||||
#endif
|
|
||||||
extern bool lcdEnabled;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
3
DCCEX.h
3
DCCEX.h
|
@ -11,7 +11,8 @@
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "WifiInterface.h"
|
#include "WifiInterface.h"
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
|
#include "LCD_Implementation.h"
|
||||||
|
#include "freeMemory.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -47,6 +47,7 @@ const int HASH_KEYWORD_DCC = 6436;
|
||||||
const int HASH_KEYWORD_SLOW = -17209;
|
const int HASH_KEYWORD_SLOW = -17209;
|
||||||
const int HASH_KEYWORD_PROGBOOST = -6353;
|
const int HASH_KEYWORD_PROGBOOST = -6353;
|
||||||
const int HASH_KEYWORD_EEPROM = -7168;
|
const int HASH_KEYWORD_EEPROM = -7168;
|
||||||
|
const int HASH_KEYWORD_LIMIT = 27413;
|
||||||
|
|
||||||
int DCCEXParser::stashP[MAX_PARAMS];
|
int DCCEXParser::stashP[MAX_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
@ -95,6 +96,7 @@ void DCCEXParser::loop(Stream &stream)
|
||||||
buffer[bufferLength++] = ch;
|
buffer[bufferLength++] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Sensor::checkAll(&stream); // Update and print changes
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
||||||
|
@ -358,11 +360,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'Q': // SENSORS <Q>
|
case 'Q': // SENSORS <Q>
|
||||||
Sensor::checkAll();
|
Sensor::printAll(stream);
|
||||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
|
||||||
{
|
|
||||||
StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 's': // <s>
|
case 's': // <s>
|
||||||
|
@ -425,7 +423,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
||||||
|
|
||||||
switch (params)
|
switch (params)
|
||||||
{
|
{
|
||||||
|
|
||||||
case 2: // <Z ID ACTIVATE>
|
case 2: // <Z ID ACTIVATE>
|
||||||
{
|
{
|
||||||
Output *o = Output::get(p[0]);
|
Output *o = Output::get(p[0]);
|
||||||
|
@ -437,11 +435,16 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 3: // <Z ID PIN INVERT>
|
case 3: // <Z ID PIN INVERT>
|
||||||
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("<O>"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 1: // <Z ID>
|
case 1: // <Z ID>
|
||||||
return Output::remove(p[0]);
|
if (!Output::remove(p[0]))
|
||||||
|
return false;
|
||||||
|
StringFormatter::send(stream, F("<O>"));
|
||||||
|
return true;
|
||||||
|
|
||||||
case 0: // <Z>
|
case 0: // <Z>
|
||||||
{
|
{
|
||||||
|
@ -465,19 +468,19 @@ bool DCCEXParser::parsef(Print *stream, int params, int p[])
|
||||||
// convenient for other processing
|
// convenient for other processing
|
||||||
if (params == 2)
|
if (params == 2)
|
||||||
{
|
{
|
||||||
byte groupcode = p[1] & 0xE0;
|
byte instructionField = p[1] & 0xE0; // 1110 0000
|
||||||
if (groupcode == 0x80)
|
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);
|
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||||
funcmap(p[0], normalized, 0, 4);
|
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);
|
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
|
||||||
}
|
funcmap(p[0], p[1], 5, 8);
|
||||||
else if (groupcode == 0xA0)
|
else
|
||||||
{
|
funcmap(p[0], p[1], 9, 12);
|
||||||
funcmap(p[0], p[1], 9, 12);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (params == 3)
|
if (params == 3)
|
||||||
|
@ -549,15 +552,20 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
||||||
switch (params)
|
switch (params)
|
||||||
{
|
{
|
||||||
case 3: // <S id pin pullup> create sensor. pullUp indicator (0=LOW/1=HIGH)
|
case 3: // <S id pin pullup> 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("<O>"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 1: // S id> remove sensor
|
case 1: // S id> remove sensor
|
||||||
if (Sensor::remove(p[0]))
|
if (!Sensor::remove(p[0]))
|
||||||
return true;
|
return false;
|
||||||
break;
|
StringFormatter::send(stream, F("<O>"));
|
||||||
|
return true;
|
||||||
|
|
||||||
case 0: // <S> lit sensor states
|
case 0: // <S> lit sensor states
|
||||||
|
if (Sensor::firstSensor == NULL)
|
||||||
|
return false;
|
||||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||||
{
|
{
|
||||||
StringFormatter::send(stream, F("<Q %d %d %d>"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
StringFormatter::send(stream, F("<Q %d %d %d>"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
||||||
|
@ -586,7 +594,11 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF>
|
case HASH_KEYWORD_ACK: // <D ACK ON/OFF>
|
||||||
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;
|
return true;
|
||||||
|
|
||||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||||
|
|
|
@ -128,7 +128,7 @@ void DCCWaveform::checkPowerOverload() {
|
||||||
|
|
||||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||||
lastSampleTaken = millis();
|
lastSampleTaken = millis();
|
||||||
int tripValue= motorDriver->rawCurrentTripValue;
|
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||||
if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
|
if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
|
||||||
tripValue=progTripValue;
|
tripValue=progTripValue;
|
||||||
|
|
||||||
|
@ -292,9 +292,12 @@ int DCCWaveform::getLastCurrent() {
|
||||||
// (yes I know I could have subclassed the main track but...)
|
// (yes I know I could have subclassed the main track but...)
|
||||||
|
|
||||||
void DCCWaveform::setAckBaseline() {
|
void DCCWaveform::setAckBaseline() {
|
||||||
if (isMainTrack) return;
|
if (isMainTrack) return;
|
||||||
ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor);
|
int baseline = motorDriver->getCurrentRaw();
|
||||||
if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->raw2mA(ackThreshold));
|
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() {
|
void DCCWaveform::setAckPending() {
|
||||||
|
|
|
@ -76,6 +76,9 @@ class DCCWaveform {
|
||||||
autoPowerOff=false;
|
autoPowerOff=false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inline void setAckLimit(int mA) {
|
||||||
|
ackLimitmA = mA;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static VirtualTimer * interruptTimer;
|
static VirtualTimer * interruptTimer;
|
||||||
|
@ -115,9 +118,10 @@ class DCCWaveform {
|
||||||
unsigned int power_good_counter = 0;
|
unsigned int power_good_counter = 0;
|
||||||
|
|
||||||
// ACK management (Prog track only)
|
// ACK management (Prog track only)
|
||||||
bool ackPending;
|
volatile bool ackPending;
|
||||||
bool ackDetected;
|
volatile bool ackDetected;
|
||||||
int ackThreshold;
|
int ackThreshold;
|
||||||
|
int ackLimitmA = 60;
|
||||||
int ackMaxCurrent;
|
int ackMaxCurrent;
|
||||||
unsigned long ackCheckStart; // millis
|
unsigned long ackCheckStart; // millis
|
||||||
unsigned int ackCheckDuration; // millis
|
unsigned int ackCheckDuration; // millis
|
||||||
|
|
1
DIAG.h
1
DIAG.h
|
@ -20,4 +20,5 @@
|
||||||
#define DIAG_h
|
#define DIAG_h
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#define DIAG StringFormatter::diag
|
#define DIAG StringFormatter::diag
|
||||||
|
#define LCD StringFormatter::lcd
|
||||||
#endif
|
#endif
|
||||||
|
|
22
EEStore.cpp
22
EEStore.cpp
|
@ -1,3 +1,23 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
|
@ -77,7 +97,7 @@ void EEStore::dump(int num) {
|
||||||
DIAG(F("\nAddr 0x char\n"));
|
DIAG(F("\nAddr 0x char\n"));
|
||||||
for (int n=0 ; n<num; n++) {
|
for (int n=0 ; n<num; n++) {
|
||||||
EEPROM.get(n, b);
|
EEPROM.get(n, b);
|
||||||
DIAG(F("%d %x %c\n"),n,b,isascii(b) ? b : ' ');
|
DIAG(F("%d %x %c\n"),n,b,isprint(b) ? b : ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
79
LCDDisplay.cpp
Normal file
79
LCDDisplay.cpp
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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; row++) rowBuffer[row][0]='\0';
|
||||||
|
topRow=-1; // loop2 will fill from row 0
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDDisplay::setRow(byte line) {
|
||||||
|
hotRow=line;
|
||||||
|
hotCol=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t LCDDisplay::write(uint8_t b) {
|
||||||
|
if (hotRow>=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<lcdRows;slot++) {
|
||||||
|
int rowNext=nextFilledRow();
|
||||||
|
if (rowNext==rowFirst){
|
||||||
|
// we have wrapped around and not filled the screen
|
||||||
|
topRow=-1; // start again at first row next time.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setRowNative(slot);
|
||||||
|
writeNative(rowBuffer[rowNext]);
|
||||||
|
}
|
||||||
|
displayNative();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LCDDisplay::nextFilledRow() {
|
||||||
|
for (int rx=1;rx<=MAX_LCD_ROWS;rx++) {
|
||||||
|
topRow++;
|
||||||
|
topRow %= MAX_LCD_ROWS;
|
||||||
|
if (rowBuffer[topRow][0]) return topRow;
|
||||||
|
}
|
||||||
|
return -1; // No slots filled
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
63
LCDDisplay.h
Normal file
63
LCDDisplay.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef LCDDisplay_h
|
||||||
|
#define LCDDisplay_h
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// 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
|
55
LCD_Implementation.h
Normal file
55
LCD_Implementation.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 <Wire.h>
|
||||||
|
#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
|
||||||
|
|
||||||
|
|
||||||
|
|
37
LCD_LCD.h
Normal file
37
LCD_LCD.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <LiquidCrystal_I2C.h>
|
||||||
|
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(); }
|
27
LCD_NONE.h
Normal file
27
LCD_NONE.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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(){}
|
||||||
|
|
57
LCD_OLED.h
Normal file
57
LCD_OLED.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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.h>
|
||||||
|
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(); }
|
||||||
|
|
|
@ -29,12 +29,16 @@ class MotorDriver {
|
||||||
virtual int getCurrentRaw();
|
virtual int getCurrentRaw();
|
||||||
virtual unsigned int raw2mA( int raw);
|
virtual unsigned int raw2mA( int raw);
|
||||||
virtual int mA2raw( unsigned int mA);
|
virtual int mA2raw( unsigned int mA);
|
||||||
|
inline int getRawCurrentTripValue() {
|
||||||
|
return rawCurrentTripValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
|
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
|
||||||
float senseFactor;
|
float senseFactor;
|
||||||
unsigned int tripMilliamps;
|
unsigned int tripMilliamps;
|
||||||
int rawCurrentTripValue;
|
int rawCurrentTripValue;
|
||||||
const byte UNUSED_PIN = 255;
|
const byte UNUSED_PIN = 255;
|
||||||
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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)
|
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)
|
if(num>0)
|
||||||
EEPROM.put(num,data.oStatus);
|
EEPROM.put(num,data.oStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
99
RingStream.cpp
Normal file
99
RingStream.cpp
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
_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==_pos_read) {
|
||||||
|
_overflow=true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_count++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RingStream::read() {
|
||||||
|
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||||
|
byte b=_buffer[_pos_read];
|
||||||
|
_pos_read++;
|
||||||
|
if (_pos_read==_len) _pos_read=0;
|
||||||
|
_overflow=false;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int RingStream::count() {
|
||||||
|
return (read()<<8) | read();
|
||||||
|
}
|
||||||
|
|
||||||
|
int RingStream::freeSpace() {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count);
|
||||||
|
// just throw it away
|
||||||
|
_pos_write=_mark;
|
||||||
|
_overflow=false;
|
||||||
|
return false; // commit failed
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
47
RingStream.h
Normal file
47
RingStream.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class RingStream : public Print {
|
||||||
|
|
||||||
|
public:
|
||||||
|
RingStream( const uint16_t len);
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t b);
|
||||||
|
using Print::write;
|
||||||
|
int read();
|
||||||
|
int count();
|
||||||
|
int freeSpace();
|
||||||
|
void mark(uint8_t b);
|
||||||
|
bool commit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _len;
|
||||||
|
int _pos_write;
|
||||||
|
int _pos_read;
|
||||||
|
bool _overflow;
|
||||||
|
int _mark;
|
||||||
|
int _count;
|
||||||
|
byte * _buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
59
Sensors.cpp
59
Sensors.cpp
|
@ -65,27 +65,62 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
||||||
|
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
|
#include "StringFormatter.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// checks one defined sensors and prints _changed_ sensor state
|
||||||
|
// to stream unless stream is NULL in which case only internal
|
||||||
|
// state is updated. Then advances to next sensor which will
|
||||||
|
// be checked att next invocation.
|
||||||
|
//
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
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){
|
if (firstSensor == NULL) return;
|
||||||
tt->active=true;
|
if (readingSensor == NULL) readingSensor=firstSensor;
|
||||||
} else if(tt->active && tt->signal>0.9){
|
|
||||||
tt->active=false;
|
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
|
||||||
}
|
}
|
||||||
} // loop over all sensors
|
} 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
|
} // 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){
|
Sensor *Sensor::create(int snum, int pin, int pullUp){
|
||||||
|
@ -108,7 +143,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp){
|
||||||
tt->data.pin=pin;
|
tt->data.pin=pin;
|
||||||
tt->data.pullUp=(pullUp==0?LOW:HIGH);
|
tt->data.pullUp=(pullUp==0?LOW:HIGH);
|
||||||
tt->active=false;
|
tt->active=false;
|
||||||
tt->signal=1;
|
tt->latchdelay=0;
|
||||||
pinMode(pin,INPUT); // set mode to input
|
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
|
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
|
||||||
|
|
||||||
|
@ -137,6 +172,7 @@ bool Sensor::remove(int n){
|
||||||
else
|
else
|
||||||
pp->nextSensor=tt->nextSensor;
|
pp->nextSensor=tt->nextSensor;
|
||||||
|
|
||||||
|
if (readingSensor==tt) readingSensor=tt->nextSensor;
|
||||||
free(tt);
|
free(tt);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -174,3 +210,4 @@ void Sensor::store(){
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Sensor *Sensor::firstSensor=NULL;
|
Sensor *Sensor::firstSensor=NULL;
|
||||||
|
Sensor *Sensor::readingSensor=NULL;
|
||||||
|
|
|
@ -31,16 +31,18 @@ struct SensorData {
|
||||||
|
|
||||||
struct Sensor{
|
struct Sensor{
|
||||||
static Sensor *firstSensor;
|
static Sensor *firstSensor;
|
||||||
|
static Sensor *readingSensor;
|
||||||
SensorData data;
|
SensorData data;
|
||||||
boolean active;
|
boolean active;
|
||||||
float signal;
|
byte latchdelay;
|
||||||
Sensor *nextSensor;
|
Sensor *nextSensor;
|
||||||
static void load();
|
static void load();
|
||||||
static void store();
|
static void store();
|
||||||
static Sensor *create(int, int, int);
|
static Sensor *create(int, int, int);
|
||||||
static Sensor* get(int);
|
static Sensor* get(int);
|
||||||
static bool remove(int);
|
static bool remove(int);
|
||||||
static void checkAll();
|
static void checkAll(Print *);
|
||||||
|
static void printAll(Print *);
|
||||||
}; // Sensor
|
}; // Sensor
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#define __FlashStringHelper char
|
#define __FlashStringHelper char
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "LCDDisplay.h"
|
||||||
|
|
||||||
bool Diag::ACK=false;
|
bool Diag::ACK=false;
|
||||||
bool Diag::CMD=false;
|
bool Diag::CMD=false;
|
||||||
bool Diag::WIFI=false;
|
bool Diag::WIFI=false;
|
||||||
|
@ -44,6 +46,21 @@ void StringFormatter::diag( const __FlashStringHelper* input...) {
|
||||||
send2(diagSerial,input,args);
|
send2(diagSerial,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_start(args, input);
|
||||||
|
send2(LCDDisplay::lcdDisplay,input,args);
|
||||||
|
}
|
||||||
|
|
||||||
void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) {
|
void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
|
@ -56,7 +73,6 @@ void StringFormatter::send(Print & stream, const __FlashStringHelper* input...)
|
||||||
send2(&stream,input,args);
|
send2(&stream,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list 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
|
// thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output
|
||||||
|
@ -66,6 +82,13 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va
|
||||||
char c=pgm_read_byte_near(flash+i);
|
char c=pgm_read_byte_near(flash+i);
|
||||||
if (c=='\0') return;
|
if (c=='\0') return;
|
||||||
if(c!='%') { stream->print(c); continue; }
|
if(c!='%') { stream->print(c); continue; }
|
||||||
|
|
||||||
|
bool formatContinues=false;
|
||||||
|
byte formatWidth=0;
|
||||||
|
bool formatLeft=false;
|
||||||
|
do {
|
||||||
|
|
||||||
|
formatContinues=false;
|
||||||
i++;
|
i++;
|
||||||
c=pgm_read_byte_near(flash+i);
|
c=pgm_read_byte_near(flash+i);
|
||||||
switch(c) {
|
switch(c) {
|
||||||
|
@ -73,14 +96,34 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va
|
||||||
case 'c': stream->print((char) va_arg(args, int)); break;
|
case 'c': stream->print((char) va_arg(args, int)); break;
|
||||||
case 's': stream->print(va_arg(args, char*)); break;
|
case 's': stream->print(va_arg(args, char*)); break;
|
||||||
case 'e': printEscapes(stream,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 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break;
|
||||||
case 'd': stream->print(va_arg(args, int), DEC); break;
|
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
||||||
case 'l': stream->print(va_arg(args, long), DEC); break;
|
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
||||||
case 'b': stream->print(va_arg(args, int), BIN); break;
|
case 'b': stream->print(va_arg(args, int), BIN); break;
|
||||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||||
case 'x': stream->print(va_arg(args, int), HEX); break;
|
case 'x': stream->print(va_arg(args, int), HEX); break;
|
||||||
case 'f': stream->print(va_arg(args, double), 2); 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);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +137,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) {
|
void StringFormatter::printEscape( char c) {
|
||||||
printEscape(diagSerial,c);
|
printEscape(diagSerial,c);
|
||||||
}
|
}
|
||||||
|
@ -109,4 +163,27 @@ void StringFormatter::printEscape(Print * stream, char c) {
|
||||||
default: stream->print(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(digits<width) {
|
||||||
|
stream->print(' ');
|
||||||
|
digits++;
|
||||||
|
}
|
||||||
|
if (!formatLeft) stream->print(value, DEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#define __FlashStringHelper char
|
#define __FlashStringHelper char
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "LCDDisplay.h"
|
||||||
class Diag {
|
class Diag {
|
||||||
public:
|
public:
|
||||||
static bool ACK;
|
static bool ACK;
|
||||||
|
@ -42,16 +43,19 @@ class StringFormatter
|
||||||
static void send(Print & serial, const __FlashStringHelper* input...);
|
static void send(Print & serial, const __FlashStringHelper* input...);
|
||||||
|
|
||||||
static void printEscapes(Print * serial,char * input);
|
static void printEscapes(Print * serial,char * input);
|
||||||
|
static void printEscapes(Print * serial,const __FlashStringHelper* input);
|
||||||
static void printEscape(Print * serial, char c);
|
static void printEscape(Print * serial, char c);
|
||||||
|
|
||||||
// DIAG support
|
// DIAG support
|
||||||
static Print * diagSerial;
|
static Print * diagSerial;
|
||||||
static void diag( const __FlashStringHelper* input...);
|
static void diag( const __FlashStringHelper* input...);
|
||||||
|
static void lcd(byte row, const __FlashStringHelper* input...);
|
||||||
static void printEscapes(char * input);
|
static void printEscapes(char * input);
|
||||||
static void printEscape( char c);
|
static void printEscape( char c);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void send2(Print * serial, const __FlashStringHelper* input,va_list args);
|
static void send2(Print * serial, const __FlashStringHelper* input,va_list args);
|
||||||
|
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
||||||
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -99,17 +99,9 @@ WiThrottle::~WiThrottle() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::parse(Print & stream, byte * cmdx) {
|
void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
|
|
||||||
// we have to take a copy of the cmd buffer as the reply will get built into the cmdx
|
byte * cmd=cmdx;
|
||||||
byte local[150];
|
|
||||||
for (byte i=0;i<sizeof(local);i++) {
|
|
||||||
local[i]=cmdx[i];
|
|
||||||
if (!cmdx[i]) break;
|
|
||||||
}
|
|
||||||
local[149]='\0'; // prevent runaway parser
|
|
||||||
|
|
||||||
byte * cmd=local;
|
|
||||||
|
|
||||||
heartBeat=millis();
|
heartBeat=millis();
|
||||||
if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd);
|
if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd);
|
||||||
|
@ -213,7 +205,7 @@ int WiThrottle::getLocoId(byte * cmd) {
|
||||||
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
|
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
|
||||||
return getInt(cmd+1);
|
return getInt(cmd+1);
|
||||||
}
|
}
|
||||||
void WiThrottle::multithrottle(Print & stream, byte * cmd){
|
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||||
char throttleChar=cmd[1];
|
char throttleChar=cmd[1];
|
||||||
int locoid=getLocoId(cmd+3); // -1 for *
|
int locoid=getLocoId(cmd+3); // -1 for *
|
||||||
byte * aval=cmd;
|
byte * aval=cmd;
|
||||||
|
@ -239,8 +231,11 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
|
||||||
myLocos[loco].throttle=throttleChar;
|
myLocos[loco].throttle=throttleChar;
|
||||||
myLocos[loco].cab=locoid;
|
myLocos[loco].cab=locoid;
|
||||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
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)
|
//Get known Fn states from DCC
|
||||||
// for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],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);
|
||||||
|
}
|
||||||
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<;>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<;>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
|
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||||
|
@ -261,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.
|
// 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);
|
// DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
||||||
switch (aval[0]) {
|
switch (aval[0]) {
|
||||||
|
@ -339,10 +334,19 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
|
||||||
return WiTSpeed + 1; //offset others by 1
|
return WiTSpeed + 1; //offset others by 1
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::loop() {
|
void WiThrottle::loop(RingStream * stream) {
|
||||||
// for each WiThrottle, check the heartbeat
|
// for each WiThrottle, check the heartbeat
|
||||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||||
wt->checkHeartbeat();
|
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
|
||||||
|
* stream->commit()
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::checkHeartbeat() {
|
void WiThrottle::checkHeartbeat() {
|
||||||
|
|
11
WiThrottle.h
11
WiThrottle.h
|
@ -19,6 +19,7 @@
|
||||||
#ifndef WiThrottle_h
|
#ifndef WiThrottle_h
|
||||||
#define WiThrottle_h
|
#define WiThrottle_h
|
||||||
|
|
||||||
|
#include "RingStream.h"
|
||||||
|
|
||||||
struct MYLOCO {
|
struct MYLOCO {
|
||||||
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
||||||
|
@ -27,8 +28,8 @@ struct MYLOCO {
|
||||||
|
|
||||||
class WiThrottle {
|
class WiThrottle {
|
||||||
public:
|
public:
|
||||||
static void loop();
|
static void loop(RingStream * stream);
|
||||||
void parse(Print & stream, byte * cmd);
|
void parse(RingStream * stream, byte * cmd);
|
||||||
static WiThrottle* getThrottle( int wifiClient);
|
static WiThrottle* getThrottle( int wifiClient);
|
||||||
static bool annotateLeftRight;
|
static bool annotateLeftRight;
|
||||||
private:
|
private:
|
||||||
|
@ -56,9 +57,9 @@ class WiThrottle {
|
||||||
bool lastPowerState; // last power state sent to this client
|
bool lastPowerState; // last power state sent to this client
|
||||||
int DCCToWiTSpeed(int DCCSpeed);
|
int DCCToWiTSpeed(int DCCSpeed);
|
||||||
int WiTToDCCSpeed(int WiTSpeed);
|
int WiTToDCCSpeed(int WiTSpeed);
|
||||||
void multithrottle(Print & stream, byte * cmd);
|
void multithrottle(RingStream * stream, byte * cmd);
|
||||||
void locoAction(Print & stream, byte* aval, char throttleChar, int cab);
|
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||||
void accessory(Print & stream, byte* cmd);
|
void accessory(RingStream *, byte* cmd);
|
||||||
void checkHeartbeat();
|
void checkHeartbeat();
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "WifiInboundHandler.h"
|
#include "WifiInboundHandler.h"
|
||||||
|
#include "RingStream.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
|
@ -16,14 +17,10 @@ void WifiInboundHandler::loop() {
|
||||||
|
|
||||||
WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
|
WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
|
||||||
wifiStream=ESStream;
|
wifiStream=ESStream;
|
||||||
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) {
|
|
||||||
clientStatus[clientId]=UNUSED;
|
|
||||||
// Note buffer is 1 byte longer than MemStream is told
|
|
||||||
// so that we can always inject a '\0' at stream->available()
|
|
||||||
clientBuffer[clientId]=new byte[MAX_WIFI_BUFFER+1];
|
|
||||||
clientStream[clientId]=new MemStream(clientBuffer[clientId], MAX_WIFI_BUFFER);
|
|
||||||
}
|
|
||||||
clientPendingCIPSEND=-1;
|
clientPendingCIPSEND=-1;
|
||||||
|
inboundRing=new RingStream(INBOUND_RING);
|
||||||
|
outboundRing=new RingStream(OUTBOUND_RING);
|
||||||
|
pendingCipsend=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,38 +28,48 @@ WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
|
||||||
// +IPD,x,lll:data is stored in streamer[x]
|
// +IPD,x,lll:data is stored in streamer[x]
|
||||||
// Other input returns
|
// Other input returns
|
||||||
void WifiInboundHandler::loop1() {
|
void WifiInboundHandler::loop1() {
|
||||||
|
// First handle all inbound traffic events because they will block the sending
|
||||||
// First handle all inbound traffic events
|
if (loop2()!=INBOUND_IDLE) return;
|
||||||
if (loop2()!=INBOUND_IDLE) return;
|
|
||||||
|
|
||||||
|
WiThrottle::loop(outboundRing);
|
||||||
|
|
||||||
// if nothing is already CIPSEND pending, we can CIPSEND one reply
|
// if nothing is already CIPSEND pending, we can CIPSEND one reply
|
||||||
if (clientPendingCIPSEND<0) {
|
if (clientPendingCIPSEND<0) {
|
||||||
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) {
|
clientPendingCIPSEND=outboundRing->read();
|
||||||
if (clientStatus[clientId]==REPLY_PENDING) {
|
if (clientPendingCIPSEND>=0) {
|
||||||
clientPendingCIPSEND=clientId;
|
currentReplySize=outboundRing->count();
|
||||||
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientId, clientStream[clientId]->available());
|
pendingCipsend=true;
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientId, clientStream[clientId]->available());
|
}
|
||||||
clientStatus[clientId]=CIPSEND_PENDING;
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if something waiting to close we can call one of them
|
|
||||||
|
if (pendingCipsend) {
|
||||||
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) {
|
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||||
if (clientStatus[clientId]==CLOSE_AFTER_SEND) {
|
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||||
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), clientId);
|
pendingCipsend=false;
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), clientId);
|
|
||||||
clientStatus[clientId]=UNUSED;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (clientStatus[clientId]==READY_TO_PROCESS) {
|
|
||||||
processCommand(clientId);
|
|
||||||
|
// if something waiting to execute, we can call it
|
||||||
|
int clientId=inboundRing->read();
|
||||||
|
if (clientId>=0) {
|
||||||
|
int count=inboundRing->count();
|
||||||
|
if (Diag::WIFI) DIAG(F("\nWifi EXEC: %d %d:"),clientId,count);
|
||||||
|
byte cmd[count+1];
|
||||||
|
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
|
||||||
|
cmd[count]=0;
|
||||||
|
if (Diag::WIFI) DIAG(F("%e\n"),cmd);
|
||||||
|
|
||||||
|
outboundRing->mark(clientId); // remember start of outbound data
|
||||||
|
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||||
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
|
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
|
||||||
|
@ -78,7 +85,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (loopState) {
|
switch (loopState) {
|
||||||
case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED
|
case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR, SEND OK
|
||||||
|
|
||||||
if (ch == '+') {
|
if (ch == '+') {
|
||||||
loopState = IPD;
|
loopState = IPD;
|
||||||
|
@ -86,10 +93,14 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ch=='>') {
|
if (ch=='>') {
|
||||||
if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),clientStream[clientPendingCIPSEND]->available());
|
if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize);
|
||||||
wifiStream->write(clientBuffer[clientPendingCIPSEND], clientStream[clientPendingCIPSEND]->available());
|
for (int i=0;i<currentReplySize;i++) {
|
||||||
clientStatus[clientPendingCIPSEND]=clientCloseAfterReply[clientPendingCIPSEND]? CLOSE_AFTER_SEND: UNUSED;
|
int cout=outboundRing->read();
|
||||||
|
wifiStream->write(cout);
|
||||||
|
if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise
|
||||||
|
}
|
||||||
clientPendingCIPSEND=-1;
|
clientPendingCIPSEND=-1;
|
||||||
|
pendingCipsend=false;
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -98,21 +109,32 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ch=='S') { // SEND OK probably
|
||||||
|
loopState=SKIPTOEND;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
|
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
|
||||||
if (clientPendingCIPSEND>=0) {
|
pendingCipsend=(clientPendingCIPSEND>=0);
|
||||||
clientStatus[clientPendingCIPSEND]=REPLY_PENDING;
|
|
||||||
clientPendingCIPSEND=-1;
|
|
||||||
}
|
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ch>='0' && ch<=('0'+MAX_CLIENTS)) {
|
if (ch>='0' && ch<='9') {
|
||||||
runningClientId=ch-'0';
|
runningClientId=ch-'0';
|
||||||
loopState=GOT_CLIENT_ID;
|
loopState=GOT_CLIENT_ID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
|
||||||
|
if (clientPendingCIPSEND>=0) {
|
||||||
|
// A CIPSEND was errored... just toss it away
|
||||||
|
purgeCurrentCIPSEND();
|
||||||
|
}
|
||||||
|
loopState=SKIPTOEND;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -133,7 +155,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IPD4_CLIENT: // reading connection id
|
case IPD4_CLIENT: // reading connection id
|
||||||
if (ch >= '0' || ch <('0'+MAX_CLIENTS)){
|
if (ch >= '0' || ch <='9'){
|
||||||
runningClientId=ch-'0';
|
runningClientId=ch-'0';
|
||||||
loopState=IPD5;
|
loopState=IPD5;
|
||||||
}
|
}
|
||||||
|
@ -151,8 +173,14 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
loopState=ANYTHING;
|
loopState=ANYTHING;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
clientStream[runningClientId]->flush(); // prepare streamer for input
|
if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength);
|
||||||
clientStatus[runningClientId]=INBOUND_ARRIVING;
|
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(runningClientId);
|
||||||
loopState=IPD_DATA;
|
loopState=IPD_DATA;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -160,28 +188,30 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IPD_DATA: // reading data
|
case IPD_DATA: // reading data
|
||||||
clientStream[runningClientId]->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer.
|
inboundRing->write(ch);
|
||||||
// This protects against buffer overflows even with things as innocent
|
|
||||||
// as a browser which send massive, irrlevent HTTP headers.
|
|
||||||
dataLength--;
|
dataLength--;
|
||||||
if (dataLength == 0) {
|
if (dataLength == 0) {
|
||||||
clientStatus[runningClientId]=READY_TO_PROCESS;
|
inboundRing->commit();
|
||||||
loopState = ANYTHING;
|
loopState = ANYTHING;
|
||||||
}
|
}
|
||||||
break;
|
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
|
case GOT_CLIENT_ID: // got x before CLOSE or CONNECTED
|
||||||
loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND;
|
loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GOT_CLIENT_ID2: // got "x," before CLOSE or CONNECTED
|
case GOT_CLIENT_ID2: // got "x,"
|
||||||
loopState=(ch=='C') ? GOT_CLIENT_ID3: SKIPTOEND;
|
if (ch=='C') {
|
||||||
|
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
||||||
|
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
||||||
|
}
|
||||||
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GOT_CLIENT_ID3: // got "x C" before CLOSE or CONNECTED (which is ignored)
|
|
||||||
if(ch=='L') clientStatus[runningClientId]=UNUSED;
|
|
||||||
loopState=SKIPTOEND;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SKIPTOEND: // skipping for /n
|
case SKIPTOEND: // skipping for /n
|
||||||
if (ch=='\n') loopState=ANYTHING;
|
if (ch=='\n') loopState=ANYTHING;
|
||||||
|
@ -191,24 +221,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY;
|
return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||||
void WifiInboundHandler::processCommand(byte clientId) {
|
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||||
clientStatus[clientId]=PROCESSING;
|
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d\n"),clientPendingCIPSEND,currentReplySize);
|
||||||
byte * buffer=clientBuffer[clientId];
|
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||||
MemStream * streamer=clientStream[clientId];
|
pendingCipsend=false;
|
||||||
buffer[streamer->available()]='\0';
|
clientPendingCIPSEND=-1;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef WifiInboundHandler_h
|
#ifndef WifiInboundHandler_h
|
||||||
#define WifiInboundHandler_h
|
#define WifiInboundHandler_h
|
||||||
|
|
||||||
#include "MemStream.h"
|
#include "RingStream.h"
|
||||||
#include "DCCEXParser.h"
|
#include "WiThrottle.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
class WifiInboundHandler {
|
class WifiInboundHandler {
|
||||||
|
@ -14,9 +14,7 @@ class WifiInboundHandler {
|
||||||
|
|
||||||
static WifiInboundHandler * singleton;
|
static WifiInboundHandler * singleton;
|
||||||
|
|
||||||
static const byte MAX_CLIENTS=5;
|
|
||||||
static const byte MAX_WIFI_BUFFER=255;
|
|
||||||
|
|
||||||
enum INBOUND_STATE {
|
enum INBOUND_STATE {
|
||||||
INBOUND_BUSY, // keep calling in loop()
|
INBOUND_BUSY, // keep calling in loop()
|
||||||
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
|
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
|
||||||
|
@ -35,38 +33,31 @@ class WifiInboundHandler {
|
||||||
IPD5, // got +IPD,c
|
IPD5, // got +IPD,c
|
||||||
IPD6_LENGTH, // got +IPD,c, reading length
|
IPD6_LENGTH, // got +IPD,c, reading length
|
||||||
IPD_DATA, // got +IPD,c,ll,: collecting data
|
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_ID, // clientid prefix to CONNECTED / CLOSED
|
||||||
GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED
|
GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED
|
||||||
GOT_CLIENT_ID3 // clientid prefix to CONNECTED / CLOSED
|
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);
|
WifiInboundHandler(Stream * ESStream);
|
||||||
void loop1();
|
void loop1();
|
||||||
INBOUND_STATE loop2();
|
INBOUND_STATE loop2();
|
||||||
void processCommand(byte clientId);
|
void purgeCurrentCIPSEND();
|
||||||
Stream * wifiStream;
|
Stream * wifiStream;
|
||||||
|
|
||||||
DCCEXParser *parser;
|
static const int INBOUND_RING = 512;
|
||||||
|
static const int OUTBOUND_RING = 2048;
|
||||||
|
|
||||||
|
RingStream * inboundRing;
|
||||||
|
RingStream * outboundRing;
|
||||||
|
|
||||||
LOOP_STATE loopState=ANYTHING;
|
LOOP_STATE loopState=ANYTHING;
|
||||||
int runningClientId; // latest client inbound processing data or CLOSE
|
int runningClientId; // latest client inbound processing data or CLOSE
|
||||||
int dataLength; // dataLength of +IPD
|
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 clientPendingCIPSEND=-1;
|
||||||
|
int currentReplySize;
|
||||||
|
bool pendingCipsend;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#include "WiThrottle.h"
|
|
||||||
#include "WifiInboundHandler.h"
|
#include "WifiInboundHandler.h"
|
||||||
|
|
||||||
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
|
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
|
||||||
|
@ -57,7 +57,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||||
const __FlashStringHelper *hostname,
|
const __FlashStringHelper *hostname,
|
||||||
const int port) {
|
const int port) {
|
||||||
|
|
||||||
bool wifiUp = false;
|
wifiSerialState wifiUp = WIFI_NOAT;
|
||||||
|
|
||||||
#if NUM_SERIAL == 0
|
#if NUM_SERIAL == 0
|
||||||
// no warning about unused parameters.
|
// no warning about unused parameters.
|
||||||
|
@ -75,7 +75,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||||
|
|
||||||
// Other serials are tried, depending on hardware.
|
// Other serials are tried, depending on hardware.
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
if (!wifiUp)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial2.begin(serial_link_speed);
|
Serial2.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
|
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
|
||||||
|
@ -83,18 +83,29 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 2
|
#if NUM_SERIAL > 2
|
||||||
if (!wifiUp)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial3.begin(serial_link_speed);
|
Serial3.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
|
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return wifiUp;
|
if (wifiUp == WIFI_NOAT) // here and still not AT commands found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DCCEXParser::setAtCommandCallback(ATCommand);
|
||||||
|
// CAUTION... ONLY CALL THIS ONCE
|
||||||
|
WifiInboundHandler::setup(wifiStream);
|
||||||
|
if (wifiUp == WIFI_CONNECTED)
|
||||||
|
connected = true;
|
||||||
|
else
|
||||||
|
connected = false;
|
||||||
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||||
const __FlashStringHelper* hostname, int port) {
|
const __FlashStringHelper* hostname, int port) {
|
||||||
|
wifiSerialState wifiState;
|
||||||
static uint8_t ntry = 0;
|
static uint8_t ntry = 0;
|
||||||
ntry++;
|
ntry++;
|
||||||
|
|
||||||
|
@ -102,22 +113,25 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid
|
||||||
|
|
||||||
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
|
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
|
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||||
checkForOK(200, OK_SEARCH, true);
|
checkForOK(200, OK_SEARCH, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
DCCEXParser::setAtCommandCallback(ATCommand);
|
|
||||||
WifiInboundHandler::setup(wifiStream);
|
|
||||||
|
|
||||||
DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED"));
|
DIAG(F("\n++ Wifi Setup %S ++\n"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
||||||
return connected;
|
return wifiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||||
const __FlashStringHelper* hostname, int port) {
|
const __FlashStringHelper* hostname, int port) {
|
||||||
bool ipOK = false;
|
bool ipOK = false;
|
||||||
bool oldCmd = false;
|
bool oldCmd = false;
|
||||||
|
|
||||||
|
@ -129,12 +143,12 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
|
||||||
if (checkForOK(200,IPD_SEARCH, true)) {
|
if (checkForOK(200,IPD_SEARCH, true)) {
|
||||||
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
|
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
|
||||||
// loopstate=4; // carry on from correct place... or not as the case may be
|
// 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?
|
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
||||||
if(!checkForOK(200, OK_SEARCH, true))
|
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
|
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
|
checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console
|
||||||
|
@ -220,8 +234,9 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
|
||||||
if (oldCmd) {
|
if (oldCmd) {
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
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);
|
int i=0;
|
||||||
checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
|
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 {
|
} else {
|
||||||
|
|
||||||
|
@ -233,18 +248,20 @@ 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
|
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
|
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
|
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);
|
DIAG(F("\nPORT=%d\n"),port);
|
||||||
|
|
||||||
return true;
|
return WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -295,7 +312,6 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
|
||||||
|
|
||||||
void WifiInterface::loop() {
|
void WifiInterface::loop() {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
WiThrottle::loop();
|
|
||||||
WifiInboundHandler::loop();
|
WifiInboundHandler::loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,11 @@
|
||||||
#ifndef WifiInterface_h
|
#ifndef WifiInterface_h
|
||||||
#define WifiInterface_h
|
#define WifiInterface_h
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "MemStream.h"
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
|
||||||
|
|
||||||
class WifiInterface
|
class WifiInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -37,11 +38,11 @@ public:
|
||||||
static void ATCommand(const byte *command);
|
static void ATCommand(const byte *command);
|
||||||
|
|
||||||
private:
|
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);
|
const __FlashStringHelper *hostname, int port);
|
||||||
static Stream *wifiStream;
|
static Stream *wifiStream;
|
||||||
static DCCEXParser parser;
|
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);
|
const __FlashStringHelper *hostname, int port);
|
||||||
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
||||||
static bool connected;
|
static bool connected;
|
||||||
|
@ -50,8 +51,5 @@ private:
|
||||||
static int datalength;
|
static int datalength;
|
||||||
static int connectionId;
|
static int connectionId;
|
||||||
static unsigned long loopTimeoutStart;
|
static unsigned long loopTimeoutStart;
|
||||||
static const byte MAX_WIFI_BUFFER = 250;
|
|
||||||
static byte buffer[MAX_WIFI_BUFFER + 1];
|
|
||||||
static MemStream * streamer;
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -70,37 +70,17 @@ The configuration file for DCC++ EX Command Station
|
||||||
//
|
//
|
||||||
// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset.
|
// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset.
|
||||||
// or one using a Hitachi HD44780.
|
// or one using a Hitachi HD44780.
|
||||||
//
|
// OR an I2C Oled screen.
|
||||||
// To enable, uncomment the line below and make sure only the correct LIB_TYPE line
|
// To enable, uncomment one of the lines below
|
||||||
// is uncommented below to select the library used for your LCD backpack
|
|
||||||
|
|
||||||
//#define ENABLE_LCD
|
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
||||||
|
// #define LCD_DRIVER 0x3F,16,2
|
||||||
#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
|
|
||||||
|
|
||||||
|
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||||
|
// This will not work on a UNO due to memory constraints
|
||||||
|
// #define OLED_DRIVER 128,32
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Enable custom command filtering
|
// Enable warning as memory gets depleted
|
||||||
#define ENABLE_CUSTOM_FILTER false
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Enable custom command filtering
|
|
||||||
#define ENABLE_CUSTOM_CALLBACK false
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Enable custom command filtering
|
|
||||||
#define ENABLE_FREE_MEM_WARNING false
|
#define ENABLE_FREE_MEM_WARNING false
|
||||||
|
|
Loading…
Reference in New Issue
Block a user