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;
|
||||
|
||||
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()
|
||||
|
||||
@ -40,8 +73,30 @@ void setup() {
|
||||
// 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;
|
||||
|
||||
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
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
@ -53,11 +108,11 @@ void setup() {
|
||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
||||
|
||||
DCC::begin(STANDARD_MOTOR_SHIELD);
|
||||
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
void loop()
|
||||
{
|
||||
// The main sketch has responsibilities during loop()
|
||||
|
||||
// Responsibility 1: Handle DCC background processes
|
||||
@ -68,7 +123,19 @@ void loop() {
|
||||
serialParser.loop(Serial);
|
||||
|
||||
// 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();
|
||||
|
52
DCC.h
52
DCC.h
@ -24,9 +24,11 @@
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int result);
|
||||
|
||||
enum ackOp { // Program opcodes for the ack Manager
|
||||
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
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
@ -55,11 +57,10 @@ SKIPTARGET=0xFF // jump to target
|
||||
const byte MAX_LOCOS = 50;
|
||||
#endif
|
||||
|
||||
|
||||
class DCC {
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1);
|
||||
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
@ -75,6 +76,7 @@ class DCC {
|
||||
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);
|
||||
@ -90,8 +92,12 @@ class DCC {
|
||||
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 {
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
@ -103,6 +109,8 @@ private:
|
||||
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);
|
||||
@ -123,8 +131,6 @@ private:
|
||||
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)
|
||||
|
||||
|
||||
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||
@ -138,4 +144,32 @@ private:
|
||||
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
|
||||
|
315
DCCEXParser.cpp
315
DCCEXParser.cpp
@ -1,7 +1,8 @@
|
||||
/*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
* © 2020, Harald Barth.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -20,18 +21,16 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "WifiInterface.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Outputs.h"
|
||||
#include "Sensors.h"
|
||||
#include "freeMemory.h"
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
const char VERSION[] PROGMEM ="0.1.9";
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int HASH_KEYWORD_PROG = -29718;
|
||||
@ -46,7 +45,7 @@ const int HASH_KEYWORD_ACK=3113;
|
||||
const int HASH_KEYWORD_ON = 2657;
|
||||
const int HASH_KEYWORD_DCC = 6436;
|
||||
const int HASH_KEYWORD_SLOW = -17209;
|
||||
|
||||
const int HASH_KEYWORD_PROGBOOST = -6353;
|
||||
|
||||
int DCCEXParser::stashP[MAX_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
@ -60,35 +59,45 @@ bool DCCEXParser::stashBusy;
|
||||
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
|
||||
|
||||
DCCEXParser::DCCEXParser() {}
|
||||
void DCCEXParser::flush() {
|
||||
if (Diag::CMD) DIAG(F("\nBuffer flush"));
|
||||
void DCCEXParser::flush()
|
||||
{
|
||||
if (Diag::CMD)
|
||||
DIAG(F("\nBuffer flush"));
|
||||
bufferLength = 0;
|
||||
inCommandPayload = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::loop(Stream & stream) {
|
||||
while(stream.available()) {
|
||||
if (bufferLength==MAX_BUFFER) {
|
||||
void DCCEXParser::loop(Stream &stream)
|
||||
{
|
||||
while (stream.available())
|
||||
{
|
||||
if (bufferLength == MAX_BUFFER)
|
||||
{
|
||||
flush();
|
||||
}
|
||||
char ch = stream.read();
|
||||
if (ch == '<') {
|
||||
if (ch == '<')
|
||||
{
|
||||
inCommandPayload = true;
|
||||
bufferLength = 0;
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
else if (ch == '>') {
|
||||
else if (ch == '>')
|
||||
{
|
||||
buffer[bufferLength] = '\0';
|
||||
parse(&stream, buffer, false); // Parse this allowing async responses
|
||||
inCommandPayload = false;
|
||||
break;
|
||||
} else if(inCommandPayload) {
|
||||
}
|
||||
else if (inCommandPayload)
|
||||
{
|
||||
buffer[bufferLength++] = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DCCEXParser::splitValues( int result[MAX_PARAMS], const byte * cmd) {
|
||||
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
||||
{
|
||||
byte state = 1;
|
||||
byte parameterCount = 0;
|
||||
int runningValue = 0;
|
||||
@ -96,16 +105,21 @@ void DCCEXParser::loop(Stream & stream) {
|
||||
bool signNegative = false;
|
||||
|
||||
// clear all parameters in case not enough found
|
||||
for (int i=0;i<MAX_PARAMS;i++) result[i]=0;
|
||||
for (int i = 0; i < MAX_PARAMS; i++)
|
||||
result[i] = 0;
|
||||
|
||||
while(parameterCount<MAX_PARAMS) {
|
||||
while (parameterCount < MAX_PARAMS)
|
||||
{
|
||||
byte hot = *remainingCmd;
|
||||
|
||||
switch (state) {
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case 1: // skipping spaces before a param
|
||||
if (hot==' ') break;
|
||||
if (hot == '\0' || hot=='>') return parameterCount;
|
||||
if (hot == ' ')
|
||||
break;
|
||||
if (hot == '\0' || hot == '>')
|
||||
return parameterCount;
|
||||
state = 2;
|
||||
continue;
|
||||
|
||||
@ -113,15 +127,18 @@ void DCCEXParser::loop(Stream & stream) {
|
||||
signNegative = false;
|
||||
runningValue = 0;
|
||||
state = 3;
|
||||
if (hot!='-') continue;
|
||||
if (hot != '-')
|
||||
continue;
|
||||
signNegative = true;
|
||||
break;
|
||||
case 3: // building a parameter
|
||||
if (hot>='0' && hot<='9') {
|
||||
if (hot >= '0' && hot <= '9')
|
||||
{
|
||||
runningValue = 10 * runningValue + (hot - '0');
|
||||
break;
|
||||
}
|
||||
if (hot>='A' && hot<='Z') {
|
||||
if (hot >= 'A' && hot <= 'Z')
|
||||
{
|
||||
// Since JMRI got modified to send keywords in some rare cases, we need this
|
||||
// Super Kluge to turn keywords into a hash value that can be recognised later
|
||||
runningValue = ((runningValue << 5) + runningValue) ^ hot;
|
||||
@ -138,75 +155,102 @@ void DCCEXParser::loop(Stream & stream) {
|
||||
}
|
||||
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter) {
|
||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterCallback = filter;
|
||||
}
|
||||
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
||||
{
|
||||
atCommandCallback = callback;
|
||||
}
|
||||
|
||||
// See documentation on DCC class for info on this section
|
||||
void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
if (Diag::CMD) DIAG(F("\nPARSING:%s\n"),com);
|
||||
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||
{
|
||||
if (Diag::CMD)
|
||||
DIAG(F("\nPARSING:%s\n"), com);
|
||||
(void)EEPROM; // tell compiler not to warn thi is unused
|
||||
int p[MAX_PARAMS];
|
||||
while (com[0]=='<' || com[0]==' ') com++; // strip off any number of < or spaces
|
||||
while (com[0] == '<' || com[0] == ' ')
|
||||
com++; // strip off any number of < or spaces
|
||||
byte params = splitValues(p, com);
|
||||
byte opcode = com[0];
|
||||
|
||||
if (filterCallback) filterCallback(stream,opcode,params,p);
|
||||
if (filterCallback)
|
||||
filterCallback(stream, opcode, params, p);
|
||||
|
||||
// Functions return from this switch if complete, break from switch implies error <X> to send
|
||||
switch(opcode) {
|
||||
case '\0': return; // filterCallback asked us to ignore
|
||||
switch (opcode)
|
||||
{
|
||||
case '\0':
|
||||
return; // filterCallback asked us to ignore
|
||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||
{
|
||||
int cab;
|
||||
int tspeed;
|
||||
int direction;
|
||||
|
||||
if (params==4) { // <t REGISTER CAB SPEED DIRECTION>
|
||||
if (params == 4)
|
||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||
cab = p[1];
|
||||
tspeed = p[2];
|
||||
direction = p[3];
|
||||
}
|
||||
else if (params==3) { // <t CAB SPEED DIRECTION>
|
||||
else if (params == 3)
|
||||
{ // <t CAB SPEED DIRECTION>
|
||||
cab = p[0];
|
||||
tspeed = p[1];
|
||||
direction = p[2];
|
||||
}
|
||||
else break;
|
||||
else
|
||||
break;
|
||||
|
||||
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds
|
||||
// to DCC 0=stop, 1= emergency stop, 2-127 speeds
|
||||
if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code
|
||||
if (tspeed<0) tspeed=1; // emergency stop DCC speed
|
||||
else if (tspeed>0) tspeed++; // map 1-126 -> 2-127
|
||||
if (cab == 0 && tspeed>1) break; // ignore broadcasts of speed>1
|
||||
if (tspeed > 126 || tspeed < -1)
|
||||
break; // invalid JMRI speed code
|
||||
if (tspeed < 0)
|
||||
tspeed = 1; // emergency stop DCC speed
|
||||
else if (tspeed > 0)
|
||||
tspeed++; // map 1-126 -> 2-127
|
||||
if (cab == 0 && tspeed > 1)
|
||||
break; // ignore broadcasts of speed>1
|
||||
|
||||
if (direction<0 || direction>1) break; // invalid direction code
|
||||
if (direction < 0 || direction > 1)
|
||||
break; // invalid direction code
|
||||
|
||||
DCC::setThrottle(cab, tspeed, direction);
|
||||
if (params==4) StringFormatter::send(stream,F("<T %d %d %d>"), p[0], p[2],p[3]);
|
||||
else StringFormatter::send(stream,F("<O>"));
|
||||
if (params == 4)
|
||||
StringFormatter::send(stream, F("<T %d %d %d>"), p[0], p[2], p[3]);
|
||||
else
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return;
|
||||
}
|
||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||
if (parsef(stream,params,p)) return;
|
||||
if (parsef(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
if(p[2] != (p[2] & 1)) return;
|
||||
if (p[2] != (p[2] & 1))
|
||||
return;
|
||||
DCC::setAccessory(p[0], p[1], p[2] == 1);
|
||||
return;
|
||||
|
||||
case 'T': // TURNOUT <T ...>
|
||||
if (parseT(stream,params,p)) return;
|
||||
if (parseT(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'Z': // OUTPUT <Z ...>
|
||||
if (parseZ(stream,params,p)) return;
|
||||
if (parseZ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'S': // SENSOR <S ...>
|
||||
if (parseS(stream,params,p)) return;
|
||||
if (parseS(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
|
||||
@ -218,37 +262,46 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
return;
|
||||
|
||||
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
|
||||
return;
|
||||
|
||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||
if (params==2) { // <V CV VALUE>
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (params == 2)
|
||||
{ // <V CV VALUE>
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking);
|
||||
return;
|
||||
}
|
||||
if (params==3) {
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (params == 3)
|
||||
{
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking);
|
||||
return;
|
||||
|
||||
|
||||
case 'R': // READ CV ON PROG
|
||||
if (params==3) { // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (params == 3)
|
||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::readCV(p[0], callback_R, blocking);
|
||||
return;
|
||||
}
|
||||
if (params==0) { // <R> New read loco id
|
||||
if (!stashCallback(stream,p)) break;
|
||||
if (params == 0)
|
||||
{ // <R> New read loco id
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::getLocoId(callback_Rloco, blocking);
|
||||
return;
|
||||
}
|
||||
@ -256,17 +309,22 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG]>
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
if (params>1) break;
|
||||
if (params > 1)
|
||||
break;
|
||||
{
|
||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
||||
if (params==0) {
|
||||
if (params == 0)
|
||||
{
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c>"), opcode);
|
||||
return;
|
||||
}
|
||||
switch (p[0]) {
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_MAIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
StringFormatter::send(stream, F("<p%c MAIN>"), opcode);
|
||||
@ -274,18 +332,21 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
|
||||
case HASH_KEYWORD_PROG:
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c PROG>"), opcode);
|
||||
return;
|
||||
case HASH_KEYWORD_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode==POWERMODE::ON) {
|
||||
if (mode == POWERMODE::ON)
|
||||
{
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
StringFormatter::send(stream, F("<p1 JOIN>"), opcode);
|
||||
}
|
||||
else StringFormatter::send(stream,F("<p0>"));
|
||||
else
|
||||
StringFormatter::send(stream, F("<p0>"));
|
||||
return;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -297,14 +358,15 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::checkAll();
|
||||
for(Sensor * tt=Sensor::firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||
{
|
||||
StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum);
|
||||
}
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream,F("<iDCC-EX-API / V-%S G-%S>"), VERSION, F(GITHUB_SHA));
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
// TODO Send stats of speed reminders table
|
||||
// TODO send status of turnouts etc etc
|
||||
return;
|
||||
@ -324,7 +386,8 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
return;
|
||||
|
||||
case 'D': // < >
|
||||
if (parseD(stream,params,p)) return;
|
||||
if (parseD(stream, params, p))
|
||||
return;
|
||||
return;
|
||||
|
||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||
@ -332,17 +395,22 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
return;
|
||||
|
||||
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
||||
if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"),p[0],p[1],p[2]?F("ON"):F("OFF"));
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
|
||||
DCC::setFn(p[0], p[1], p[2] == 1);
|
||||
return;
|
||||
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
WifiInterface::ATCommand(com);
|
||||
if (atCommandCallback) {
|
||||
atCommandCallback(com);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("\nOpcode=%c params=%d\n"), opcode, params);
|
||||
for (int i=0;i<params;i++) DIAG(F("p[%d]=%d (0x%x)\n"),i,p[i],p[i]);
|
||||
for (int i = 0; i < params; i++)
|
||||
DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]);
|
||||
break;
|
||||
|
||||
} // end of opcode switch
|
||||
@ -351,15 +419,17 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
StringFormatter::send(stream, F("<X>"));
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseZ( Print * stream,int params, int p[]){
|
||||
bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
||||
{
|
||||
|
||||
|
||||
switch (params) {
|
||||
switch (params)
|
||||
{
|
||||
|
||||
case 2: // <Z ID ACTIVATE>
|
||||
{
|
||||
Output *o = Output::get(p[0]);
|
||||
if(o==NULL) return false;
|
||||
if (o == NULL)
|
||||
return false;
|
||||
o->activate(p[1]);
|
||||
StringFormatter::send(stream, F("<Y %d %d>"), p[0], p[1]);
|
||||
}
|
||||
@ -375,7 +445,8 @@ bool DCCEXParser::parseZ( Print * stream,int params, int p[]){
|
||||
case 0: // <Z>
|
||||
{
|
||||
bool gotone = false;
|
||||
for(Output * tt=Output::firstOutput;tt!=NULL;tt=tt->nextOutput){
|
||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||
{
|
||||
gotone = true;
|
||||
StringFormatter::send(stream, F("<Y %d %d %d %d>"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
|
||||
}
|
||||
@ -387,44 +458,57 @@ bool DCCEXParser::parseZ( Print * stream,int params, int p[]){
|
||||
}
|
||||
|
||||
//===================================
|
||||
bool DCCEXParser::parsef(Print * stream, int params, int p[]) {
|
||||
bool DCCEXParser::parsef(Print *stream, int params, int p[])
|
||||
{
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
if (params==2) {
|
||||
if (params == 2)
|
||||
{
|
||||
byte groupcode = p[1] & 0xE0;
|
||||
if (groupcode == 0x80) {
|
||||
if (groupcode == 0x80)
|
||||
{
|
||||
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||
funcmap(p[0], normalized, 0, 4);
|
||||
}
|
||||
else if (groupcode == 0xC0) {
|
||||
else if (groupcode == 0xC0)
|
||||
{
|
||||
funcmap(p[0], p[1], 5, 8);
|
||||
}
|
||||
else if (groupcode == 0xA0) {
|
||||
else if (groupcode == 0xA0)
|
||||
{
|
||||
funcmap(p[0], p[1], 9, 12);
|
||||
}
|
||||
}
|
||||
if (params==3) {
|
||||
if (p[1]==222) funcmap(p[0],p[2],13,20);
|
||||
else if (p[1]==223) funcmap(p[0],p[2],21,28);
|
||||
if (params == 3)
|
||||
{
|
||||
if (p[1] == 222)
|
||||
funcmap(p[0], p[2], 13, 20);
|
||||
else if (p[1] == 223)
|
||||
funcmap(p[0], p[2], 21, 28);
|
||||
}
|
||||
(void)stream; // NO RESPONSE
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) {
|
||||
for (int i=fstart;i<=fstop;i++) {
|
||||
void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop)
|
||||
{
|
||||
for (int i = fstart; i <= fstop; i++)
|
||||
{
|
||||
DCC::setFn(cab, i, value & 1);
|
||||
value >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
//===================================
|
||||
bool DCCEXParser::parseT(Print * stream, int params, int p[]) {
|
||||
switch(params){
|
||||
bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
||||
{
|
||||
switch (params)
|
||||
{
|
||||
case 0: // <T> show all turnouts
|
||||
{
|
||||
bool gotOne = false;
|
||||
for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){
|
||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||
{
|
||||
gotOne = true;
|
||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
|
||||
}
|
||||
@ -432,21 +516,24 @@ bool DCCEXParser::parseT(Print * stream, int params, int p[]) {
|
||||
}
|
||||
|
||||
case 1: // <T id> delete turnout
|
||||
if (!Turnout::remove(p[0])) return false;
|
||||
if (!Turnout::remove(p[0]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return true;
|
||||
|
||||
case 2: // <T id 0|1> activate turnout
|
||||
{
|
||||
Turnout *tt = Turnout::get(p[0]);
|
||||
if (!tt) return false;
|
||||
if (!tt)
|
||||
return false;
|
||||
tt->activate(p[1]);
|
||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
|
||||
}
|
||||
return true;
|
||||
|
||||
case 3: // <T id addr subaddr> define turnout
|
||||
if (!Turnout::create(p[0],p[1],p[2])) return false;
|
||||
if (!Turnout::create(p[0], p[1], p[2]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return true;
|
||||
|
||||
@ -455,19 +542,23 @@ bool DCCEXParser::parseT(Print * stream, int params, int p[]) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseS( Print * stream,int params, int p[]) {
|
||||
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)
|
||||
Sensor::create(p[0], p[1], p[2]);
|
||||
return true;
|
||||
|
||||
case 1: // S id> remove sensor
|
||||
if (Sensor::remove(p[0])) return true;
|
||||
if (Sensor::remove(p[0]))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case 0: // <S> lit sensor states
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
@ -478,10 +569,13 @@ bool DCCEXParser::parseS( Print * stream,int params, int p[]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseD( Print * stream,int params, int p[]) {
|
||||
if (params==0) return false;
|
||||
bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
||||
{
|
||||
if (params == 0)
|
||||
return false;
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
switch(p[0]){
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
return true;
|
||||
@ -509,46 +603,57 @@ bool DCCEXParser::parseD( Print * stream,int params, int p[]) {
|
||||
case HASH_KEYWORD_DCC:
|
||||
DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// CALLBACKS must be static
|
||||
bool DCCEXParser::stashCallback(Print * stream,int p[MAX_PARAMS]) {
|
||||
if (stashBusy || asyncBanned) return false;
|
||||
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS])
|
||||
{
|
||||
if (stashBusy || asyncBanned)
|
||||
return false;
|
||||
stashBusy = true;
|
||||
stashStream = stream;
|
||||
memcpy(stashP, p, MAX_PARAMS * sizeof(p[0]));
|
||||
return true;
|
||||
}
|
||||
void DCCEXParser::callback_W(int result) {
|
||||
void DCCEXParser::callback_W(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_B(int result) {
|
||||
void DCCEXParser::callback_B(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
|
||||
stashBusy = false;
|
||||
}
|
||||
void DCCEXParser::callback_Vbit(int result) {
|
||||
void DCCEXParser::callback_Vbit(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<v %d %d %d>"), stashP[0], stashP[1], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
void DCCEXParser::callback_Vbyte(int result) {
|
||||
void DCCEXParser::callback_Vbyte(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<v %d %d>"), stashP[0], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_R(int result) {
|
||||
void DCCEXParser::callback_R(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_Rloco(int result) {
|
||||
void DCCEXParser::callback_Rloco(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r %d>"), result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
|
@ -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,11 +147,14 @@ 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;
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
@ -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
|
13
MemStream.h
13
MemStream.h
@ -24,7 +24,12 @@ 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
|
||||
@ -37,7 +42,6 @@ private:
|
||||
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);
|
||||
@ -51,7 +55,12 @@ public:
|
||||
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 \
|
||||
#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, 250 , 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 \
|
||||
#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, 250 , UNUSED_PIN)
|
||||
new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 1000, UNUSED_PIN)
|
||||
|
||||
// Firebox Mk1S
|
||||
#define FIREBOX_MK1S \
|
||||
#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, 250 , UNUSED_PIN)
|
||||
new MotorDriver(30, 27, 28, 31, 29, 5.00, 1000, UNUSED_PIN)
|
||||
|
||||
// FunduMoto Motor Shield
|
||||
#define FUNDUMOTO_SHIELD \
|
||||
#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, 250 , 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,6 +82,9 @@ 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
|
||||
@ -79,10 +93,69 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
|
||||
|
||||
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"
|
||||
|
||||
// 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
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
|
||||
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) {
|
||||
// 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"
|
||||
|
||||
// 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
|
||||
@ -94,42 +167,13 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
|
||||
}
|
||||
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
||||
|
||||
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) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=3\r\n")); // configure as server or access point
|
||||
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)) {
|
||||
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"
|
||||
@ -26,10 +26,11 @@
|
||||
|
||||
typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd);
|
||||
|
||||
class WifiInterface {
|
||||
class WifiInterface
|
||||
{
|
||||
|
||||
public:
|
||||
static void setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password,
|
||||
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);
|
||||
@ -51,7 +52,6 @@ class WifiInterface {
|
||||
static unsigned long loopTimeoutStart;
|
||||
static const byte MAX_WIFI_BUFFER = 250;
|
||||
static byte buffer[MAX_WIFI_BUFFER + 1];
|
||||
static MemStream streamer;
|
||||
static MemStream * streamer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
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