mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-02-17 06:29:15 +01:00
Merge branch 'feature/config' into mergetest
Conflicts: platformio.ini
This commit is contained in:
commit
527cc92eac
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
|
||||
}
|
||||
|
10
DCC.cpp
10
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);
|
||||
}
|
||||
|
||||
@ -206,6 +208,10 @@ void DCC::setProgTrackSyncMain(bool on) {
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
|
||||
__FlashStringHelper* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
|
||||
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
W0,WACK,
|
||||
@ -568,7 +574,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();
|
||||
|
51
DCC.h
51
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
|
||||
@ -90,8 +91,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 +108,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 +130,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 +143,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
|
||||
|
305
DCCEXParser.cpp
305
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;
|
||||
@ -47,7 +46,6 @@ const int HASH_KEYWORD_ON=2657;
|
||||
const int HASH_KEYWORD_DCC = 6436;
|
||||
const int HASH_KEYWORD_SLOW = -17209;
|
||||
|
||||
|
||||
int DCCEXParser::stashP[MAX_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
|
||||
@ -60,35 +58,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 +104,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 +126,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 +154,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 +261,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 +308,20 @@ 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);
|
||||
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);
|
||||
@ -279,13 +334,14 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
|
||||
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 +353,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 +381,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 +390,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 +414,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 +440,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 +453,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 +511,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 +537,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 +564,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;
|
||||
@ -515,40 +604,46 @@ bool DCCEXParser::parseD( Print * stream,int params, int p[]) {
|
||||
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,7 +30,7 @@ const int MIN_ACK_PULSE_DURATION = 2000;
|
||||
const int MAX_ACK_PULSE_DURATION = 8500;
|
||||
|
||||
|
||||
const int PREAMBLE_BITS_MAIN = 20;
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
|
||||
|
||||
|
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);
|
||||
|
@ -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.
|
||||
@ -9,35 +12,34 @@
|
||||
// A custom hardware setup will require your sketch to create MotorDriver instances
|
||||
// similar to those defined here, WITHOUT editing this file.
|
||||
|
||||
|
||||
const byte UNUSED_PIN = 255;
|
||||
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin,
|
||||
// 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, TRIP_CURRENT_PROG, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD \
|
||||
#define POLOLU_MOTOR_SHIELD F("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)
|
||||
new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, UNUSED_PIN)
|
||||
|
||||
#endif
|
||||
|
@ -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
|
||||
|
@ -34,6 +34,7 @@ lib_deps =
|
||||
DIO2
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@ -58,3 +59,4 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
DIO2
|
||||
mathertel/LiquidCrystal_PCF8574
|
||||
|
Loading…
Reference in New Issue
Block a user