1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-23 08:06:13 +01:00

Merge pull request #2 from DCC-EX/master

update from head
This commit is contained in:
mstevetodd 2020-11-10 13:57:03 -05:00 committed by GitHub
commit 6b4199be27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 329 deletions

View File

@ -38,6 +38,10 @@ void setup()
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT);
#endif // WIFI_ON
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// Responsibility 3: Start the DCC engine.
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
// Standard supported devices have pre-configured macros but custome hardware installations require
@ -67,6 +71,9 @@ void loop()
#if WIFI_ON
WifiInterface::loop();
#endif
#if ETHERNET_ON
EthernetInterface::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use

View File

@ -48,6 +48,7 @@ const int HASH_KEYWORD_SLOW = -17209;
const int HASH_KEYWORD_PROGBOOST = -6353;
const int HASH_KEYWORD_EEPROM = -7168;
const int HASH_KEYWORD_LIMIT = 27413;
const int HASH_KEYWORD_ETHERNET = -30767;
int DCCEXParser::stashP[MAX_PARAMS];
bool DCCEXParser::stashBusy;
@ -609,6 +610,10 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
Diag::WIFI = onOff;
return true;
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF>
Diag::ETHERNET = onOff;
return true;
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
Diag::WITHROTTLE = onOff;
return true;

View File

@ -16,252 +16,63 @@
* 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 "CommandDistributor.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
EthernetInterface * EthernetInterface::singleton=NULL;
/**
* @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);
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
};
/**
* @brief Aquire IP Address from DHCP and start server
*
* @return true
* @return false
*/
EthernetInterface::EthernetInterface()
{
byte mac[]=MAC_ADDRESS;
DIAG(F("\n+++++ Ethernet Setup "));
connected=false;
if (Ethernet.begin(mac) == 0)
{
DIAG(F("begin FAILED\n"));
return;
}
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("shield not found\n"));
return;
}
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("cable not connected\n"));
return;
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), LISTEN_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
/**
@ -270,35 +81,93 @@ void EthernetInterface::setup()
*/
void EthernetInterface::loop()
{
if (!singleton) return;
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;
DIAG(F("\nEthernet Error: renewed fail\n"));
singleton=NULL;
return;
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;
DIAG(F("Ethernet Error: rebind fail\n"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
protocolHandler();
singleton->loop2();
}
void EthernetInterface::loop2()
{
// get client from the server
EthernetClient client = server->accept();
// check for new client
if (client)
{
if (Diag::ETHERNET) DIAG(F("\nEthernet: New client "));
byte socket;
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
{
if (!clients[socket])
{
// On accept() the EthernetServer doesn't track the client anymore
// so we store it in our client array
if (Diag::ETHERNET) DIAG(F("%d\n"),socket);
clients[socket] = client;
break;
}
}
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW\n"));
}
// check for incoming data from all possible clients
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
{
if (clients[socket]) {
int available=clients[socket].available();
if (available > 0) {
if (Diag::ETHERNET) DIAG(F("\nEthernet: available socket=%d,avail=%d,count="), socket, available);
// read bytes from a client
int count = clients[socket].read(buffer, MAX_ETH_BUFFER);
buffer[count] = '\0'; // terminate the string properly
if (Diag::ETHERNET) DIAG(F("%d:%e\n"), socket,buffer);
// execute with data going directly back
outboundRing->mark(socket);
CommandDistributor::parse(socket,buffer,outboundRing);
outboundRing->commit();
return; // limit the amount of processing that takes place within 1 loop() cycle.
}
}
}
// stop any clients which disconnect
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
if (clients[socket] && !clients[socket].connected()) {
clients[socket].stop();
if (Diag::ETHERNET) DIAG(F("\nEthernet: disconnect %d \n"), socket);
}
}
// handle at most 1 outbound transmission
int socketOut=outboundRing->read();
if (socketOut>=0) {
int count=outboundRing->count();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d\n"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}

View File

@ -28,6 +28,7 @@
#include <Arduino.h>
#include <avr/pgmspace.h>
#include <Ethernet.h>
#include "RingStream.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
@ -46,62 +47,28 @@
*
*/
#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)();
#define LISTEN_PORT 2560 // default listen port for the server
#define MAX_ETH_BUFFER 512
#define OUTBOUND_RING_SIZE 2048
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;
};
static void setup();
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
void loop2();
EthernetServer * server;
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
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing;
};
#endif

View File

@ -34,19 +34,20 @@
#define ReadPin digitalRead2
#endif
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin,
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
powerPin=power_pin;
signalPin=signal_pin;
signalPin2=signal_pin2;
brakePin=brake_pin;
currentPin=current_pin;
senseFactor=sense_factor;
faultPin=fault_pin;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
powerPin=power_pin;
signalPin=signal_pin;
signalPin2=signal_pin2;
brakePin=brake_pin;
currentPin=current_pin;
senseFactor=sense_factor;
faultPin=fault_pin;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
pinMode(powerPin, OUTPUT);
pinMode(brakePin, OUTPUT);
pinMode(brakePin < 0 ? -brakePin : brakePin, OUTPUT);
setBrake(false);
pinMode(signalPin, OUTPUT);
if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT);
pinMode(currentPin, INPUT);
@ -54,10 +55,32 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte
}
void MotorDriver::setPower(bool on) {
if (brakePin == -4 && on) {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
setBrake(true);
setBrake(false);
}
WritePin(powerPin, on ? HIGH : LOW);
}
void MotorDriver::setBrake( bool on) {
WritePin(brakePin, on ? HIGH : LOW);
// setBrake applies brake if on == true. So to get
// voltage from the motor bride one needs to do a
// setBrake(false).
// If the brakePin is negative that means the sense
// of the brake pin on the motor bridge is inverted
// (HIGH == release brake) and setBrake does
// compensate for that.
//
void MotorDriver::setBrake(bool on) {
bool state = on;
byte pin = brakePin;
if (brakePin < 0) {
pin=-pin;
state=!state;
}
WritePin(pin, state ? HIGH : LOW);
//DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin));
}
void MotorDriver::setSignal( bool high) {

View File

@ -20,9 +20,13 @@
#define MotorDriver_h
// Virtualised Motor shield 1-track hardware Interface
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
#define UNUSED_PIN 127 // inside int8_t
#endif
class MotorDriver {
public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
virtual void setPower( bool on);
virtual void setSignal( bool high);
virtual void setBrake( bool on);
@ -34,11 +38,10 @@ class MotorDriver {
}
private:
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
byte powerPin, signalPin, signalPin2, currentPin, faultPin;
int8_t brakePin; // negative means pin is inverted
float senseFactor;
unsigned int tripMilliamps;
int rawCurrentTripValue;
const byte UNUSED_PIN = 255;
};
#endif

View File

@ -13,10 +13,16 @@
// similar to those defined here, WITHOUT editing this file. You can put your
// custom defines in config.h.
const byte UNUSED_PIN = 255;
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
#define UNUSED_PIN 127 // inside int8_t
#endif
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin,
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
//
// If the brakePin is negative that means the sense
// of the brake pin on the motor bridge is inverted
// (HIGH == release brake)
// Arduino standard Motor Shield
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
@ -24,9 +30,19 @@ const byte UNUSED_PIN = 255;
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// Pololu Motor Shield
#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)
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, UNUSED_PIN)
//
// Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the
// pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually
// via the power pins we above use 9 and 10 as power pins and 4 as "inverted brake" which in this
// version of the code always will be high. That means this config is not usable for generating
// a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define
// the motor driver like this:
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, UNUSED_PIN)
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
// Firebox Mk1
#define FIREBOX_MK1 F("FIREBOX_MK1"), \

View File

@ -37,6 +37,7 @@ bool Diag::ACK=false;
bool Diag::CMD=false;
bool Diag::WIFI=false;
bool Diag::WITHROTTLE=false;
bool Diag::ETHERNET=false;
void StringFormatter::diag( const __FlashStringHelper* input...) {

View File

@ -34,6 +34,8 @@ class Diag {
static bool CMD;
static bool WIFI;
static bool WITHROTTLE;
static bool ETHERNET;
};
class StringFormatter

View File

@ -36,8 +36,7 @@ class WifiInboundHandler {
IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring
GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID3 // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED
};

View File

@ -27,9 +27,14 @@
#define WIFI_ON true
#else
#define WIFI_ON false
#endif
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO))
#define ETHERNET_ON true
#else
#define ETHERNET_ON false
#endif
////////////////////////////////////////////////////////////////////////////////
//
// This defines the speed at which the Arduino will communicate with the ESP8266 module.