mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-25 13:41:23 +01:00
commit
fee0a75b36
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -13,5 +13,7 @@ jobs:
|
||||
run: pip install wheel
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip
|
||||
- name: Copy generic config over
|
||||
run: cp config.example.h config.h
|
||||
- name: Compile Command Station (AVR)
|
||||
run: python -m platformio run
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ Release/*
|
||||
.gcc-flags.json
|
||||
.pio/
|
||||
.vscode/
|
||||
config.h
|
@ -1,34 +1,67 @@
|
||||
/*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is a demonstattion of setting up a DCC-EX
|
||||
* Command station to support direct connection of WiThrottle devices
|
||||
* such as "Engine Driver". If you contriol your layout through JMRI
|
||||
* then DON'T connect throttles to this wifi, connect them to JMRI.
|
||||
*
|
||||
* This is just 3 statements longer than the basic setup.
|
||||
*
|
||||
* THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT.
|
||||
* REFER TO SEPARATE EXAMPLE.
|
||||
*/
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// © 2020, Chris Harlow. All rights reserved.
|
||||
//
|
||||
// This file is a demonstattion of setting up a DCC-EX
|
||||
// Command station with optional support for direct connection of WiThrottle devices
|
||||
// such as "Engine Driver". If you contriol your layout through JMRI
|
||||
// then DON'T connect throttles to this wifi, connect them to JMRI.
|
||||
//
|
||||
// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM.
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "config.h"
|
||||
#include "DCCEX.h"
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO
|
||||
#define WIFI_BAUD 9600
|
||||
#else
|
||||
#define WIFI_BAUD 115200
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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... Enables certain diagnostics and commands
|
||||
// to be issued from the USB serial console
|
||||
// This is NOT intended for JMRI....
|
||||
// Create a serial command parser for the USB connection,
|
||||
// This supports JMRI or manual diagnostics and commands
|
||||
// to be issued from the USB serial console.
|
||||
DCCEXParser serialParser;
|
||||
|
||||
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()
|
||||
|
||||
@ -36,28 +69,50 @@ void setup() {
|
||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||
Serial.begin(115200);
|
||||
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
// NOTE: References to Serial1 are for the serial port used to connect
|
||||
// your wifi chip/shield.
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
// NOTE: References to Serial1 are for the serial port used to connect
|
||||
// your wifi chip/shield.
|
||||
|
||||
Serial1.begin(WIFI_BAUD);
|
||||
WifiInterface::setup(Serial1, F("Your network name"), F("your network password"),F("DCCEX"),3532);
|
||||
#ifdef WIFI_ON
|
||||
bool wifiUp = false;
|
||||
const __FlashStringHelper *wifiESSID = F(WIFI_SSID);
|
||||
const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD);
|
||||
const __FlashStringHelper *dccex = F(WIFI_HOSTNAME);
|
||||
const uint16_t port = IP_PORT;
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
Serial1.begin(WIFI_SERIAL_LINK_SPEED);
|
||||
wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port);
|
||||
#if NUM_SERIAL > 1
|
||||
if (!wifiUp)
|
||||
{
|
||||
Serial2.begin(WIFI_SERIAL_LINK_SPEED);
|
||||
wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port);
|
||||
}
|
||||
#if NUM_SERIAL > 2
|
||||
if (!wifiUp)
|
||||
{
|
||||
Serial3.begin(WIFI_SERIAL_LINK_SPEED);
|
||||
wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port);
|
||||
}
|
||||
#endif // >2
|
||||
#endif // >1
|
||||
#endif // WIFI_ON
|
||||
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
|
||||
// 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
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
|
||||
DCC::begin(STANDARD_MOTOR_SHIELD);
|
||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
||||
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
void loop()
|
||||
{
|
||||
// The main sketch has responsibilities during loop()
|
||||
|
||||
// Responsibility 1: Handle DCC background processes
|
||||
@ -67,8 +122,20 @@ void loop() {
|
||||
// Responsibility 2: handle any incoming commands on USB connection
|
||||
serialParser.loop(Serial);
|
||||
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
#ifdef WIFI_ON
|
||||
WifiInterface::loop();
|
||||
#endif
|
||||
|
||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
||||
#if ENABLE_FREE_MEM_WARNING
|
||||
static int ramLowWatermark = 32767; // replaced on first loop
|
||||
|
||||
int freeNow = freeMemory();
|
||||
if (freeNow < ramLowWatermark)
|
||||
{
|
||||
ramLowWatermark = freeNow;
|
||||
DIAG(F("\nFree RAM=%d\n"), ramLowWatermark);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
13
DCC.cpp
13
DCC.cpp
@ -41,8 +41,10 @@ const byte FN_GROUP_3=0x04;
|
||||
const byte FN_GROUP_4=0x08;
|
||||
const byte FN_GROUP_5=0x10;
|
||||
|
||||
__FlashStringHelper* DCC::shieldName=NULL;
|
||||
|
||||
void DCC::begin(MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
||||
void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
||||
shieldName=(__FlashStringHelper*)motorShieldName;
|
||||
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
|
||||
}
|
||||
|
||||
@ -205,6 +207,13 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
void DCC::setProgTrackSyncMain(bool on) {
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
__FlashStringHelper* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
|
||||
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
@ -568,7 +577,7 @@ void DCC::ackManagerLoop(bool blocking) {
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
DCCWaveform::progTrack.autoPowerOff=true;
|
||||
return;
|
||||
if (!blocking) return;
|
||||
}
|
||||
if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
|
178
DCC.h
178
DCC.h
@ -24,85 +24,93 @@
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int result);
|
||||
|
||||
enum ackOp { // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET=0xFF // jump to target
|
||||
enum ackOp
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
// Allocations with memory implications..!
|
||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||
#ifdef ARDUINO_AVR_UNO
|
||||
const byte MAX_LOCOS=20;
|
||||
#else
|
||||
const byte MAX_LOCOS=50;
|
||||
#endif
|
||||
#ifdef ARDUINO_AVR_UNO
|
||||
const byte MAX_LOCOS = 20;
|
||||
#else
|
||||
const byte MAX_LOCOS = 50;
|
||||
#endif
|
||||
|
||||
|
||||
class DCC {
|
||||
public:
|
||||
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1);
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static uint8_t getThrottleSpeed(int cab);
|
||||
static bool getThrottleDirection(int cab);
|
||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||
static void setFunction( int cab, byte fByte, byte eByte);
|
||||
static void setFn( int cab, byte functionNumber, bool on);
|
||||
static int changeFn( int cab, byte functionNumber, bool pressed);
|
||||
static void updateGroupflags(byte & flags, int functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate) ;
|
||||
static bool writeTextPacket( byte *b, int nBytes);
|
||||
static void setFunction(int cab, byte fByte, byte eByte);
|
||||
static void setFn(int cab, byte functionNumber, bool on);
|
||||
static int changeFn(int cab, byte functionNumber, bool pressed);
|
||||
static void updateGroupflags(byte &flags, int functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
||||
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
static void readCV(int cv, ACK_CALLBACK callback, bool blocking=false);
|
||||
static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking=false); // -1 for error
|
||||
static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ;
|
||||
static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false);
|
||||
static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ;
|
||||
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false);
|
||||
static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error
|
||||
static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
|
||||
static void getLocoId(ACK_CALLBACK callback, bool blocking=false);
|
||||
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
|
||||
|
||||
// Enhanced API functions
|
||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print * stream);
|
||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print *stream);
|
||||
|
||||
static __FlashStringHelper *getMotorShieldName();
|
||||
|
||||
private:
|
||||
struct LOCO {
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
};
|
||||
static byte loopStatus;
|
||||
static void setThrottle2( uint16_t cab, uint8_t speedCode);
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
static void updateLocoReminder(int loco, byte speedCode);
|
||||
static void setFunctionInternal( int cab, byte fByte, byte eByte);
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||
static bool issueReminder(int reg);
|
||||
static int nextLoco;
|
||||
static __FlashStringHelper *shieldName;
|
||||
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
@ -110,32 +118,58 @@ private:
|
||||
static void issueReminders();
|
||||
static void callback(int value);
|
||||
|
||||
// ACK MANAGER
|
||||
static ackOp const * ackManagerProg;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerStash;
|
||||
// ACK MANAGER
|
||||
static ackOp const *ackManagerProg;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
||||
static void ackManagerLoop(bool blocking);
|
||||
static bool checkResets(bool blocking, uint8_t numResets);
|
||||
static const int PROG_REPEATS=8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED=0x3f;
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||
static const byte WRITE_BIT_MAIN = 0xE8;
|
||||
static const byte WRITE_BYTE = 0x7C;
|
||||
static const byte VERIFY_BYTE= 0x74;
|
||||
static const byte BIT_MANIPULATE=0x78;
|
||||
static const byte WRITE_BIT=0xF0;
|
||||
static const byte VERIFY_BIT=0xE0;
|
||||
static const byte BIT_ON=0x08;
|
||||
static const byte BIT_OFF=0x00;
|
||||
static const byte VERIFY_BYTE = 0x74;
|
||||
static const byte BIT_MANIPULATE = 0x78;
|
||||
static const byte WRITE_BIT = 0xF0;
|
||||
static const byte VERIFY_BIT = 0xE0;
|
||||
static const byte BIT_ON = 0x08;
|
||||
static const byte BIT_OFF = 0x00;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
|
||||
#define ARDUINO_AVR_MEGA2560
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "UNOWIFIR2"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#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
|
||||
|
10
DCCEX.h
10
DCCEX.h
@ -1,7 +1,17 @@
|
||||
// This include is intended to visually simplify the .ino for the end users.
|
||||
// If there were any #ifdefs required they are much better handled in here.
|
||||
|
||||
#ifndef DCCEX_h
|
||||
#define DCCEX_h
|
||||
|
||||
#include "defines.h"
|
||||
#include "DCC.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "version.h"
|
||||
#include "WifiInterface.h"
|
||||
#include "EthernetInterface.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#endif
|
||||
|
929
DCCEXParser.cpp
929
DCCEXParser.cpp
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||
|
||||
struct DCCEXParser
|
||||
{
|
||||
@ -29,6 +30,7 @@ struct DCCEXParser
|
||||
void parse(Print * stream, byte * command, bool blocking);
|
||||
void flush();
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
static const int MAX_PARAMS=10; // Must not exceed this
|
||||
|
||||
private:
|
||||
@ -59,6 +61,7 @@ struct DCCEXParser
|
||||
static void callback_Vbit(int result);
|
||||
static void callback_Vbyte(int result);
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
||||
|
||||
};
|
||||
|
@ -30,6 +30,7 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
VirtualTimer * DCCWaveform::interruptTimer=NULL;
|
||||
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
|
||||
@ -123,10 +124,13 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
|
||||
void DCCWaveform::checkPowerOverload() {
|
||||
|
||||
static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static
|
||||
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= motorDriver->rawCurrentTripValue;
|
||||
if (!isMainTrack && (ackPending || progTrackSyncMain)) tripValue=ACK_CURRENT_TRIP;
|
||||
if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
|
||||
tripValue=progTripValue;
|
||||
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
@ -143,12 +147,15 @@ void DCCWaveform::checkPowerOverload() {
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPowerMode(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=motorDriver->convertToMilliamps(lastCurrent);
|
||||
unsigned int maxmA=motorDriver->convertToMilliamps(tripValue);
|
||||
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
@ -287,7 +294,7 @@ int DCCWaveform::getLastCurrent() {
|
||||
void DCCWaveform::setAckBaseline() {
|
||||
if (isMainTrack) return;
|
||||
ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor);
|
||||
if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold));
|
||||
if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->raw2mA(ackThreshold));
|
||||
}
|
||||
|
||||
void DCCWaveform::setAckPending() {
|
||||
@ -303,7 +310,7 @@ void DCCWaveform::setAckPending() {
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->convertToMilliamps(ackMaxCurrent), ackPulseDuration);
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
@ -22,15 +22,17 @@
|
||||
#include "MotorDriver.h"
|
||||
#include "ArduinoTimers.h"
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// Ack time thresholds. Unit: microseconds
|
||||
const int MIN_ACK_PULSE_DURATION = 2000;
|
||||
const int MAX_ACK_PULSE_DURATION = 8500;
|
||||
|
||||
|
||||
const int PREAMBLE_BITS_MAIN = 20;
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
|
||||
|
||||
@ -67,6 +69,7 @@ class DCCWaveform {
|
||||
void setAckPending(); //prog track only
|
||||
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
inline void doAutoPowerOff() {
|
||||
if (autoPowerOff) {
|
||||
setPowerMode(POWERMODE::OFF);
|
||||
@ -105,7 +108,9 @@ class DCCWaveform {
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
static const int ACK_CURRENT_TRIP=1000; // During ACK processing limit can be higher
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
|
304
EthernetInterface.cpp
Normal file
304
EthernetInterface.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Ethernet Interface added by Gregor Baues
|
||||
*/
|
||||
|
||||
#include "EthernetInterface.h"
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
//#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <EthernetUdp.h>
|
||||
|
||||
|
||||
// Support Functions
|
||||
/**
|
||||
* @brief Aquire IP Address from DHCP; if that fails try a statically configured address
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool EthernetInterface::setupConnection()
|
||||
{
|
||||
|
||||
singleton=this;
|
||||
|
||||
DIAG(F("\nInitialize Ethernet with DHCP:"));
|
||||
server = EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
|
||||
ip = IPAddress(IP_ADDRESS); // init with fixed IP address needed to get to the server
|
||||
connected = false; // Connection status
|
||||
streamer= new MemStream(buffer, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true); // streamer who writes the results to the buffer
|
||||
|
||||
if (Ethernet.begin(EthernetInterface::mac) == 0)
|
||||
{
|
||||
DIAG(F("\nFailed to configure Ethernet using DHCP ... Trying with fixed IP"));
|
||||
Ethernet.begin(EthernetInterface::mac, EthernetInterface::ip); // default ip address
|
||||
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
{
|
||||
DIAG(F("\nEthernet shield was not found. Sorry, can't run without hardware. :("));
|
||||
return false;
|
||||
};
|
||||
if (Ethernet.linkStatus() == LinkOFF)
|
||||
{
|
||||
DIAG(F("\nEthernet cable is not connected."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]);
|
||||
DIAG(F("\nListening on port: [%d]"), port);
|
||||
dnsip = Ethernet.dnsServerIP();
|
||||
DIAG(F("\nDNS server IP address: [%d.%d.%d.%d] "), ip[0], ip[1], ip[2], ip[3]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles command requests recieved via UDP. UDP is a connection less, unreliable protocol as it doesn't maintain state but fast.
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::udpHandler() {
|
||||
singleton->udpHandler2();
|
||||
}
|
||||
void EthernetInterface::udpHandler2()
|
||||
{
|
||||
|
||||
int packetSize = Udp.parsePacket();
|
||||
if (packetSize)
|
||||
{
|
||||
DIAG(F("\nReceived packet of size:[%d]\n"), packetSize);
|
||||
IPAddress remote = Udp.remoteIP();
|
||||
DIAG(F("From: [%d.%d.%d.%d:"), remote[0], remote[1], remote[2], remote[3]);
|
||||
char portBuffer[6];
|
||||
DIAG(F("%s]\n"), utoa(Udp.remotePort(), portBuffer, 10)); // DIAG has issues with unsigend int's so go through utoa
|
||||
|
||||
// read the packet into packetBufffer
|
||||
Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
|
||||
|
||||
DIAG(F("Command: [%s]\n"), packetBuffer);
|
||||
|
||||
streamer->flush();
|
||||
|
||||
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
|
||||
|
||||
ethParser.parse(streamer, (byte *)packetBuffer, true); // set to true so it is sync cf. WifiInterface
|
||||
|
||||
if (streamer->available() == 0)
|
||||
{
|
||||
DIAG(F("\nNo response\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// send the reply
|
||||
DIAG(F("Response: %s\n"), (char *)buffer);
|
||||
Udp.write((char *)buffer);
|
||||
Udp.endPacket();
|
||||
}
|
||||
|
||||
memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE); // reset PacktBuffer
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles command requests recieved via TCP. Supports up to the max# of simultaneous requests which is 8. The connection gets closed as soon as we finished processing
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::tcpHandler()
|
||||
{
|
||||
singleton->tcpHandler2();
|
||||
}
|
||||
void EthernetInterface::tcpHandler2()
|
||||
{
|
||||
// get client from the server
|
||||
EthernetClient client = getServer().accept();
|
||||
|
||||
// check for new client
|
||||
if (client)
|
||||
{
|
||||
for (byte i = 0; i < MAX_SOCK_NUM; i++)
|
||||
{
|
||||
if (!clients[i])
|
||||
{
|
||||
// On accept() the EthernetServer doesn't track the client anymore
|
||||
// so we store it in our client array
|
||||
clients[i] = client;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for incoming data from all possible clients
|
||||
for (byte i = 0; i < MAX_SOCK_NUM; i++)
|
||||
{
|
||||
if (clients[i] && clients[i].available() > 0)
|
||||
{
|
||||
// read bytes from a client
|
||||
int count = clients[i].read(buffer, MAX_ETH_BUFFER);
|
||||
buffer[count] = '\0'; // terminate the string properly
|
||||
DIAG(F("\nReceived packet of size:[%d]\n"), count);
|
||||
DIAG(F("From Client #: [%d]\n"), i);
|
||||
DIAG(F("Command: [%s]\n"), buffer);
|
||||
|
||||
// as we use buffer for recv and send we have to reset the write position
|
||||
streamer->setBufferContentPosition(0, 0);
|
||||
|
||||
ethParser.parse(streamer, buffer, true); // set to true to that the execution in DCC is sync
|
||||
|
||||
if (streamer->available() == 0)
|
||||
{
|
||||
DIAG(F("No response\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[streamer->available()] = '\0'; // mark end of buffer, so it can be used as a string later
|
||||
DIAG(F("Response: %s\n"), (char *)buffer);
|
||||
if (clients[i].connected())
|
||||
{
|
||||
clients[i].write(buffer, streamer->available());
|
||||
}
|
||||
}
|
||||
}
|
||||
// stop any clients which disconnect
|
||||
for (byte i = 0; i < MAX_SOCK_NUM; i++)
|
||||
{
|
||||
if (clients[i] && !clients[i].connected())
|
||||
{
|
||||
DIAG(F("Disconnect client #%d \n"), i);
|
||||
clients[i].stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Class Functions
|
||||
/**
|
||||
* @brief Setup Ethernet Connection
|
||||
*
|
||||
* @param pt Protocol used
|
||||
* @param localPort Port number for the connection
|
||||
*/
|
||||
void EthernetInterface::setup(protocolType pt, uint16_t localPort)
|
||||
{
|
||||
DIAG(F("\n++++++ Ethernet Setup In Progress ++++++++\n"));
|
||||
port = localPort;
|
||||
if (setupConnection())
|
||||
{
|
||||
DIAG(F("\nProtocol: [%s]\n"), pt ? "UDP" : "TCP");
|
||||
switch (pt)
|
||||
{
|
||||
case UDP:
|
||||
{
|
||||
if (Udp.begin(localPort))
|
||||
{
|
||||
connected = true;
|
||||
protocolHandler = udpHandler;
|
||||
}
|
||||
else
|
||||
{
|
||||
DIAG(F("\nUDP client failed to start"));
|
||||
connected = false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
case TCP:
|
||||
{
|
||||
Ethernet.begin(mac, ip);
|
||||
EthernetServer server(localPort);
|
||||
setServer(server);
|
||||
server.begin();
|
||||
connected = true;
|
||||
protocolHandler = tcpHandler;
|
||||
break;
|
||||
};
|
||||
default:
|
||||
{
|
||||
DIAG(F("Unkown Ethernet protocol; Setup failed"));
|
||||
connected = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
connected = false;
|
||||
};
|
||||
DIAG(F("\n++++++ Ethernet Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED"));
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Setup Ethernet on default port and user choosen protocol
|
||||
*
|
||||
* @param pt Protocol UDP or TCP
|
||||
*/
|
||||
void EthernetInterface::setup(protocolType pt)
|
||||
{
|
||||
setup(pt, LISTEN_PORT);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Ethernet setup with defaults TCP / Listen Port
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
setup(TCP, LISTEN_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main loop for the EthernetInterface
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
switch (Ethernet.maintain())
|
||||
{
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("\nError: renewed fail"));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
//renewed success
|
||||
DIAG(F("\nRenewed success: "));
|
||||
ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"),ip[0], ip[1], ip[2], ip[3]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Error: rebind fail"));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
//rebind success
|
||||
DIAG(F("Rebind success"));
|
||||
ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
protocolHandler();
|
||||
}
|
107
EthernetInterface.h
Normal file
107
EthernetInterface.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Ethernet Interface added by Gregor Baues
|
||||
*/
|
||||
|
||||
#ifndef EthernetInterface_h
|
||||
#define EthernetInterface_h
|
||||
|
||||
#include "DCCEXParser.h"
|
||||
#include "MemStream.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <Ethernet.h>
|
||||
|
||||
/* some generated mac addresses as EthernetShields don't have one by default in HW.
|
||||
* Sometimes they come on a sticker on the EthernetShield then use this address otherwise
|
||||
* just choose one from below or generate one yourself. Only condition is that there is no
|
||||
* other device on your network with the same Mac address.
|
||||
*
|
||||
* 52:b8:8a:8e:ce:21
|
||||
* e3:e9:73:e1:db:0d
|
||||
* 54:2b:13:52:ac:0c
|
||||
* c2:d8:d4:7d:7c:cb
|
||||
* 86:cf:fa:9f:07:79
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Network Configuration
|
||||
*
|
||||
*/
|
||||
#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
|
||||
#define IP_ADDRESS 10, 0, 0, 101 // Just in case we don't get an adress from DHCP try a static one; make sure
|
||||
// this one is not used elsewhere and corresponds to your network layout
|
||||
#define LISTEN_PORT 3366 // default listen port for the server
|
||||
#define MAX_ETH_BUFFER 250
|
||||
|
||||
typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd);
|
||||
|
||||
enum protocolType {
|
||||
TCP,
|
||||
UDP
|
||||
};
|
||||
|
||||
typedef void (*protocolCallback)();
|
||||
|
||||
class EthernetInterface {
|
||||
|
||||
private:
|
||||
EthernetServer server;
|
||||
|
||||
public:
|
||||
DCCEXParser ethParser;
|
||||
bool connected;
|
||||
byte mac[6];
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
IPAddress dnsip;
|
||||
|
||||
void setup(protocolType pt, uint16_t lp); // specific port nummber
|
||||
void setup(protocolType pt); // uses default port number
|
||||
void setup(); // all defaults (protocol/port)
|
||||
|
||||
protocolCallback protocolHandler;
|
||||
|
||||
void loop();
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
|
||||
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; // buffer to hold incoming UDP packet,
|
||||
uint8_t buffer[MAX_ETH_BUFFER]; // buffer provided to the streamer to be filled with the reply (used by TCP also for the recv)
|
||||
MemStream * streamer; // streamer who writes the results to the buffer
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
|
||||
bool setupConnection();
|
||||
static void udpHandler();
|
||||
static void tcpHandler();
|
||||
void udpHandler2();
|
||||
void tcpHandler2();
|
||||
EthernetUDP Udp;
|
||||
|
||||
EthernetServer getServer() {
|
||||
return server;
|
||||
};
|
||||
void setServer(EthernetServer s) {
|
||||
server = s;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
29
MemStream.h
29
MemStream.h
@ -24,34 +24,43 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#define MemStream_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <Stream.h>
|
||||
#endif
|
||||
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
class MemStream : public Stream
|
||||
{
|
||||
private:
|
||||
uint8_t * _buffer;
|
||||
const uint16_t _len;
|
||||
bool _buffer_overflow;
|
||||
uint16_t _pos_read;
|
||||
uint16_t _pos_write;
|
||||
bool _allowWrite;
|
||||
|
||||
uint8_t *_buffer;
|
||||
const uint16_t _len;
|
||||
bool _buffer_overflow;
|
||||
uint16_t _pos_read;
|
||||
uint16_t _pos_write;
|
||||
bool _allowWrite;
|
||||
|
||||
public:
|
||||
// public methods
|
||||
MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite=true);
|
||||
MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite = true);
|
||||
~MemStream() {}
|
||||
|
||||
operator const uint8_t *() const { return _buffer; }
|
||||
operator const char *() const { return (const char*)_buffer; }
|
||||
operator const char *() const { return (const char *)_buffer; }
|
||||
|
||||
uint16_t current_length() const { return _pos_write; }
|
||||
|
||||
bool listen() { return true; }
|
||||
void end() {}
|
||||
bool isListening() { return true; }
|
||||
bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; }
|
||||
bool overflow()
|
||||
{
|
||||
bool ret = _buffer_overflow;
|
||||
_buffer_overflow = false;
|
||||
return ret;
|
||||
}
|
||||
int peek();
|
||||
|
||||
virtual size_t write(uint8_t byte);
|
||||
|
@ -76,6 +76,9 @@ int MotorDriver::getCurrentRaw() {
|
||||
return analogReadFast(currentPin);
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::convertToMilliamps( int raw) {
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
return (unsigned int)(raw * senseFactor);
|
||||
}
|
||||
int MotorDriver::mA2raw( unsigned int mA) {
|
||||
return (int)(mA / senseFactor);
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ class MotorDriver {
|
||||
virtual void setSignal( bool high);
|
||||
virtual void setBrake( bool on);
|
||||
virtual int getCurrentRaw();
|
||||
virtual unsigned int convertToMilliamps( int rawValue);
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
|
||||
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
|
||||
float senseFactor;
|
||||
|
@ -1,5 +1,8 @@
|
||||
#ifndef MotorDrivers_h
|
||||
#define MotorDrivers_h
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
|
||||
// It will be overwritten if the library is updated.
|
||||
@ -7,8 +10,8 @@
|
||||
// This file contains configurations for known/supported motor shields.
|
||||
// A configuration defined by macro here can be used in your sketch.
|
||||
// A custom hardware setup will require your sketch to create MotorDriver instances
|
||||
// similar to those defined here, WITHOUT editing this file.
|
||||
|
||||
// similar to those defined here, WITHOUT editing this file. You can put your
|
||||
// custom defines in config.h.
|
||||
|
||||
const byte UNUSED_PIN = 255;
|
||||
|
||||
@ -16,28 +19,28 @@ const byte UNUSED_PIN = 255;
|
||||
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
|
||||
// Arduino standard Motor Shield
|
||||
#define STANDARD_MOTOR_SHIELD \
|
||||
new MotorDriver(3 , 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN)
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD \
|
||||
new MotorDriver(4, 7, UNUSED_PIN, 9 , A0, 18, 2000, 12), \
|
||||
new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 250 , UNUSED_PIN)
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 3000, 12), \
|
||||
new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 3000, UNUSED_PIN)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 250 , UNUSED_PIN)
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 1000, UNUSED_PIN)
|
||||
|
||||
// Firebox Mk1S
|
||||
#define FIREBOX_MK1S \
|
||||
new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \
|
||||
new MotorDriver(30, 27, 28, 31, 29, 5.00, 250 , UNUSED_PIN)
|
||||
#define FIREBOX_MK1S F("FIREBOX_MK1A"), \
|
||||
new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \
|
||||
new MotorDriver(30, 27, 28, 31, 29, 5.00, 1000, UNUSED_PIN)
|
||||
|
||||
// FunduMoto Motor Shield
|
||||
#define FUNDUMOTO_SHIELD \
|
||||
new MotorDriver(10 , 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN)
|
||||
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
|
||||
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
#endif
|
||||
|
15
README.md
15
README.md
@ -12,7 +12,20 @@ The DCC++ EX Command Station consists of an Arduino micro controller fitted with
|
||||
|
||||
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders. More information about the sketch can be found in the included PDF file.
|
||||
|
||||
To utilize this sketch, you can use our automated installer, or download a zip file from this repository and open the file "CommandStation-EX.ino" after unzipping it to your Arduino IDE projects folder. Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code.
|
||||
To utilize this sketch, you can use the following methods:
|
||||
|
||||
* our automated installer
|
||||
* download a zip file from this repository (green Code button above) and unzip it
|
||||
* use git clone on this repository
|
||||
|
||||
With the manual methods you unzip or git clone to the Arduino IDE
|
||||
projects folder and then open the file "CommandStation-EX.ino" in the
|
||||
Arduino IDE. Please do not rename the folder containing the sketch
|
||||
code, nor add any files in that folder. The Arduino IDE relies on the
|
||||
structure and name of the folder to properly display and compile the
|
||||
code. If you do not run the installer, you have to copy
|
||||
config.example.h to config.h. If you do not have the standard config
|
||||
you edit config.h according to the help texts in config.h.
|
||||
|
||||
The latest production release of the Master branch is 3.0.1:
|
||||
|
||||
|
@ -85,10 +85,15 @@ ISR(TIMER2_OVF_vect)
|
||||
|
||||
#include "ATMEGA4809/Timer.h"
|
||||
|
||||
Timer TimerA(0);
|
||||
Timer TimerA(1);
|
||||
Timer TimerB(2);
|
||||
|
||||
ISR(TCA0_OVF_vect) {
|
||||
ISR(TIMER1_OVF_vect) {
|
||||
TimerA.isrCallback();
|
||||
}
|
||||
|
||||
ISR(TIMER2_OVF_vect) {
|
||||
TimerB.isrCallback();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,7 +1,8 @@
|
||||
/*
|
||||
© 2020, Chris Harlow. All rights reserved.
|
||||
© 2020, Harald Barth.
|
||||
|
||||
This file is part of Asbelos DCC API
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
This is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -16,10 +17,14 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "WifiInterface.h"
|
||||
|
||||
#include "WifiInterface.h" /* config.h and defines.h included here */
|
||||
#include <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
#include "WiThrottle.h"
|
||||
|
||||
|
||||
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
|
||||
const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n";
|
||||
const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000";
|
||||
@ -35,30 +40,36 @@ unsigned long WifiInterface::loopTimeoutStart = 0;
|
||||
int WifiInterface::datalength = 0;
|
||||
int WifiInterface::connectionId;
|
||||
byte WifiInterface::buffer[MAX_WIFI_BUFFER+1];
|
||||
MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER);
|
||||
MemStream * WifiInterface::streamer;
|
||||
Stream * WifiInterface::wifiStream = NULL;
|
||||
HTTP_CALLBACK WifiInterface::httpCallback = 0;
|
||||
|
||||
|
||||
void WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
|
||||
wifiStream = &setupStream;
|
||||
|
||||
DIAG(F("\n++++++ Wifi Setup In Progress ++++++++\n"));
|
||||
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
|
||||
|
||||
connected = setup2( SSid, password, hostname, port);
|
||||
|
||||
if (connected) {
|
||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||
checkForOK(200, OK_SEARCH, true);
|
||||
}
|
||||
streamer=new MemStream(buffer, MAX_WIFI_BUFFER);
|
||||
parser.setAtCommandCallback(ATCommand);
|
||||
|
||||
DIAG(F("\n++++++ Wifi Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED"));
|
||||
DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED"));
|
||||
return connected;
|
||||
}
|
||||
|
||||
bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
int ipOK = 0;
|
||||
bool ipOK = false;
|
||||
bool oldCmd = false;
|
||||
|
||||
char macAddress[17]; // mac address extraction
|
||||
|
||||
@ -71,65 +82,98 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
|
||||
return true;
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
||||
if(!checkForOK(200, OK_SEARCH, true))
|
||||
return false; // No AT compatible WiFi module here
|
||||
|
||||
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
|
||||
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
|
||||
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
// If the source code looks unconfigured, check if the
|
||||
// ESP8266 is preconfigured. We check the first 13 chars
|
||||
// of the password.
|
||||
if (strncmp_P("Your network ",(const char*)password,13) == 0) {
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
|
||||
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
||||
// Copy 17 byte mac address
|
||||
for (int i=0; i<17;i++) {
|
||||
while(!wifiStream->available());
|
||||
macAddress[i]=wifiStream->read();
|
||||
StringFormatter::printEscape(macAddress[i]);
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
} else {
|
||||
|
||||
if (!ipOK) {
|
||||
|
||||
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
||||
if (checkForOK(2000, OK_SEARCH, true)) {
|
||||
oldCmd=true;
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(16000, OK_SEARCH, true);
|
||||
}
|
||||
DIAG(F("\n**\n"));
|
||||
|
||||
} else {
|
||||
// later version supports CWJAP_CUR
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
||||
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(20000, OK_SEARCH, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||
ipOK = 1;
|
||||
if (ipOK) {
|
||||
// But we really only have the ESSID and password correct
|
||||
// Let's check for IP
|
||||
ipOK = false;
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipOK) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=3\r\n")); // configure as server or access point
|
||||
// If we have not managed to get this going in station mode, go for AP mode
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint.
|
||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||
|
||||
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
||||
if (checkForOK(2000, OK_SEARCH, true)) {
|
||||
// Figure out MAC addr
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
||||
// Copy 17 byte mac address
|
||||
for (int i=0; i<17;i++) {
|
||||
while(!wifiStream->available());
|
||||
macAddress[i]=wifiStream->read();
|
||||
StringFormatter::printEscape(macAddress[i]);
|
||||
}
|
||||
}
|
||||
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
||||
|
||||
if (oldCmd) {
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
|
||||
}
|
||||
DIAG(F("\n**\n"));
|
||||
|
||||
// establish the APname
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
||||
checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
|
||||
|
||||
}
|
||||
else {
|
||||
// later version supports CWJAP_CUR
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
||||
|
||||
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
checkForOK(20000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
|
||||
}
|
||||
} else {
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
||||
checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok
|
||||
@ -257,15 +301,15 @@ void WifiInterface::loop() {
|
||||
case 6: // reading for length
|
||||
if (ch == ':') loopstate = (datalength == 0) ? 99 : 7; // 99 is getout without reading next char
|
||||
else datalength = datalength * 10 + (ch - '0');
|
||||
streamer.flush(); // basically sets write point at start of buffer
|
||||
streamer->flush(); // basically sets write point at start of buffer
|
||||
break;
|
||||
case 7: // reading data
|
||||
streamer.write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer.
|
||||
streamer->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer.
|
||||
// This protects against buffer overflows even with things as innocent
|
||||
// as a browser which send massive, irrlevent HTTP headers.
|
||||
datalength--;
|
||||
if (datalength == 0) {
|
||||
buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later
|
||||
buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later
|
||||
loopstate = 99;
|
||||
}
|
||||
break;
|
||||
@ -308,8 +352,8 @@ void WifiInterface::loop() {
|
||||
loopstate = 1;
|
||||
}
|
||||
if (ch == 'K') { // assume its in SEND OK
|
||||
if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
|
||||
if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available());
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available());
|
||||
loopTimeoutStart = millis();
|
||||
loopstate = 10; // non-blocking loop waits for > before sending
|
||||
break;
|
||||
@ -322,7 +366,7 @@ void WifiInterface::loop() {
|
||||
// AT this point we have read an incoming message into the buffer
|
||||
|
||||
if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer);
|
||||
streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer
|
||||
streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer
|
||||
// SIDE EFFECT WARNING:::
|
||||
// We know that parser will read the entire buffer before starting to write to it.
|
||||
// Otherwise we would have to copy the buffer elsewhere and RAM is in short supply.
|
||||
@ -331,18 +375,18 @@ void WifiInterface::loop() {
|
||||
|
||||
// Intercept HTTP requests
|
||||
if (isHTTP()) {
|
||||
if (httpCallback) httpCallback(&streamer, buffer);
|
||||
if (httpCallback) httpCallback(streamer, buffer);
|
||||
else {
|
||||
StringFormatter::send(streamer, F("HTTP/1.1 404 Not Found\nContent-Type: text/html\nConnnection: close\n\n"));
|
||||
StringFormatter::send(streamer, F("<html><body>This is <b>not</b> a web server.<br/></body></html>"));
|
||||
}
|
||||
closeAfter = true;
|
||||
}
|
||||
else if (buffer[0] == '<') parser.parse(&streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
|
||||
else if (buffer[0] == '<') parser.parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
|
||||
|
||||
else WiThrottle::getThrottle(connectionId)->parse(streamer, buffer);
|
||||
else WiThrottle::getThrottle(connectionId)->parse(*streamer, buffer);
|
||||
|
||||
if (streamer.available() == 0) {
|
||||
if (streamer->available() == 0) {
|
||||
// No reply
|
||||
if (closeAfter) {
|
||||
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId);
|
||||
@ -352,10 +396,10 @@ void WifiInterface::loop() {
|
||||
return;
|
||||
}
|
||||
// prepare to send reply
|
||||
buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later
|
||||
if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available());
|
||||
if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
|
||||
buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later
|
||||
if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer->available());
|
||||
if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available());
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available());
|
||||
loopTimeoutStart = millis();
|
||||
loopstate = 10; // non-blocking loop waits for > before sending
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
/*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
* © 2020, Harald Barth.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,7 +17,6 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WifiInterface_h
|
||||
#define WifiInterface_h
|
||||
#include "DCCEXParser.h"
|
||||
@ -24,34 +24,34 @@
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd);
|
||||
typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd);
|
||||
|
||||
class WifiInterface {
|
||||
class WifiInterface
|
||||
{
|
||||
|
||||
public:
|
||||
static void setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port);
|
||||
static void loop();
|
||||
static void ATCommand(const byte * command);
|
||||
static void setHTTPCallback(HTTP_CALLBACK callback);
|
||||
public:
|
||||
static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
||||
const __FlashStringHelper *hostname, int port);
|
||||
static void loop();
|
||||
static void ATCommand(const byte *command);
|
||||
static void setHTTPCallback(HTTP_CALLBACK callback);
|
||||
|
||||
private:
|
||||
static Stream * wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static bool setup2( const __FlashStringHelper* SSSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port);
|
||||
static bool checkForOK(const unsigned int timeout, const char* waitfor, bool echo, bool escapeEcho=true);
|
||||
static bool isHTTP();
|
||||
static HTTP_CALLBACK httpCallback;
|
||||
static bool connected;
|
||||
static bool closeAfter;
|
||||
static byte loopstate;
|
||||
static int datalength;
|
||||
static int connectionId;
|
||||
static unsigned long loopTimeoutStart;
|
||||
static const byte MAX_WIFI_BUFFER=250;
|
||||
static byte buffer[MAX_WIFI_BUFFER+1];
|
||||
static MemStream streamer;
|
||||
private:
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
||||
const __FlashStringHelper *hostname, int port);
|
||||
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
||||
static bool isHTTP();
|
||||
static HTTP_CALLBACK httpCallback;
|
||||
static bool connected;
|
||||
static bool closeAfter;
|
||||
static byte loopstate;
|
||||
static int datalength;
|
||||
static int connectionId;
|
||||
static unsigned long loopTimeoutStart;
|
||||
static const byte MAX_WIFI_BUFFER = 250;
|
||||
static byte buffer[MAX_WIFI_BUFFER + 1];
|
||||
static MemStream * streamer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
106
config.example.h
Normal file
106
config.example.h
Normal file
@ -0,0 +1,106 @@
|
||||
/**********************************************************************
|
||||
|
||||
Config.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
COPYRIGHT (c) 2020 Fred Decker
|
||||
|
||||
The configuration file for DCC++ EX Command Station
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: Before connecting these boards and selecting one in this software
|
||||
// check the quick install guides!!! Some of these boards require a voltage
|
||||
// generating resitor on the current sense pin of the device. Failure to select
|
||||
// the correct resistor could damage the sense pin on your Arduino or destroy
|
||||
// the device.
|
||||
//
|
||||
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
|
||||
//
|
||||
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// |
|
||||
// +-----------------------v
|
||||
//
|
||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The IP port to talk to a WIFI or Ethernet shield.
|
||||
//
|
||||
#define IP_PORT 2560
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NOTE: Only supported on Arduino Mega
|
||||
// Set to false if you not even want it on the Arduino Mega
|
||||
//
|
||||
#define ENABLE_WIFI true
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE WiFi Parameters (only in effect if WIFI is on)
|
||||
//
|
||||
#define WIFI_SSID "Your network name"
|
||||
#define WIFI_PASSWORD "Your network passwd"
|
||||
#define WIFI_HOSTNAME "dccex"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
|
||||
//
|
||||
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||
//
|
||||
// Uncomment to use with Ethernet Shields
|
||||
//
|
||||
// NOTE: This is not used with ESP8266 WiFi modules.
|
||||
//
|
||||
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
|
||||
//
|
||||
// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset.
|
||||
// or one using a Hitachi HD44780.
|
||||
//
|
||||
// To enable, uncomment the line below and make sure only the correct LIB_TYPE line
|
||||
// is uncommented below to select the library used for your LCD backpack
|
||||
|
||||
//#define ENABLE_LCD
|
||||
|
||||
#ifdef ENABLE_LCD
|
||||
#define LIB_TYPE_PCF8574
|
||||
//#define LIB_TYPE_I2C
|
||||
// This defines the I2C address for the LCD device
|
||||
#define LCD_ADDRESS 0x27 //common defaults are 0x27 and 0x3F
|
||||
|
||||
// This defines the number of columns the LCD device has
|
||||
#define LCD_COLUMNS 16
|
||||
|
||||
// This defines the number of lines the LCD device has
|
||||
#define LCD_LINES 2
|
||||
#endif
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Enable custom command filtering
|
||||
#define ENABLE_CUSTOM_FILTER false
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Enable custom command filtering
|
||||
#define ENABLE_CUSTOM_CALLBACK false
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Enable custom command filtering
|
||||
#define ENABLE_FREE_MEM_WARNING false
|
45
defines.h
Normal file
45
defines.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
© 2020, Harald Barth.
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
This is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WIFI_ON: All prereqs for running with WIFI are met
|
||||
//
|
||||
#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
|
||||
#define WIFI_ON
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This defines the speed at which the Arduino will communicate with the ESP8266 module.
|
||||
// Currently only devices which can communicate at 115200 are supported.
|
||||
//
|
||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Figure out number of serial ports depending on hardware
|
||||
//
|
||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
|
||||
#define NUM_SERIAL 3
|
||||
#endif
|
||||
#ifndef NUM_SERIAL
|
||||
#define NUM_SERIAL 1
|
||||
#endif
|
42
freeMemory.cpp
Normal file
42
freeMemory.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* © 2020, Harald Barth
|
||||
*
|
||||
* This file is part of Asbelos DCC-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 "freeMemory.h"
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
#if defined(__arm__)
|
||||
extern "C" char* sbrk(int);
|
||||
#elif defined(__AVR__)
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
#else
|
||||
#error Unsupported board type
|
||||
#endif
|
||||
|
||||
|
||||
int freeMemory() {
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
#error bailed out alredy above
|
||||
#endif
|
||||
}
|
37
freeMemory.h
37
freeMemory.h
@ -1,20 +1,23 @@
|
||||
/*
|
||||
* © 2020, Harald Barth
|
||||
*
|
||||
* This file is part of DCC-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 freeMemory_h
|
||||
#define freeMemory_h
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
#ifdef __arm__
|
||||
// should use uinstd.h to define sbrk but Due causes a conflict
|
||||
extern "C" char* sbrk(int incr);
|
||||
#else // __ARM__
|
||||
extern char *__brkval;
|
||||
#endif // __arm__
|
||||
|
||||
int freeMemory() {
|
||||
char top;
|
||||
#ifdef __arm__
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#else // __arm__
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#endif // __arm__
|
||||
}
|
||||
int freeMemory();
|
||||
#endif
|
||||
|
10
objdump.bat
10
objdump.bat
@ -1,12 +1,14 @@
|
||||
ECHO ON
|
||||
FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i
|
||||
echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt
|
||||
SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf
|
||||
|
||||
avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
notepad %TMP%\OBJDUMP_%a%.txt
|
||||
EXIT
|
||||
|
@ -4,9 +4,10 @@
|
||||
ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}')
|
||||
PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH
|
||||
|
||||
avr-objdump --private=mem-usage /tmp/arduino_build_233823/Blinkhabaplus.ino.elf
|
||||
LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1)
|
||||
avr-objdump --private=mem-usage "$LASTBUILD"
|
||||
|
||||
for segment in .text .data .bss ; do
|
||||
echo '++++++++++++++++++++++++++++++++++'
|
||||
avr-objdump -x -C /tmp/arduino_build_233823/Blinkhabaplus.ino.elf | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r
|
||||
avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r
|
||||
done
|
||||
|
@ -24,6 +24,8 @@ upload_protocol = atmel-ice
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
SparkFun External EEPROM Arduino Library
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:mega2560]
|
||||
platform = atmelavr
|
||||
@ -32,6 +34,11 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
DIO2
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@ -40,6 +47,11 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
DIO2
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:unowifiR2]
|
||||
platform = atmelmegaavr
|
||||
@ -48,6 +60,11 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
DIO2
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
|
||||
[env:uno]
|
||||
platform = atmelavr
|
||||
@ -56,3 +73,8 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
DIO2
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
|
Loading…
Reference in New Issue
Block a user