From 1e065dc10735dc5dd207b7d61b8c96514e6ad847 Mon Sep 17 00:00:00 2001 From: dtworth <36244729+dtworth@users.noreply.github.com> Date: Fri, 9 Mar 2018 16:45:35 -0500 Subject: [PATCH 1/7] Sensors Only Version for NodeMCU Uses a WiFi connection only to talk to JMRI. --- DCCpp_Uno/Accessories.cpp | 239 ----------------- DCCpp_Uno/Accessories.h | 39 --- DCCpp_Uno/Comm.h | 35 --- DCCpp_Uno/Config.h | 41 +-- DCCpp_Uno/CurrentMonitor.cpp | 39 --- DCCpp_Uno/CurrentMonitor.h | 35 --- DCCpp_Uno/DCCpp_Uno.h | 117 +-------- DCCpp_Uno/DCCpp_Uno.ino | 395 +++------------------------- DCCpp_Uno/EEStore.cpp | 18 +- DCCpp_Uno/Outputs.cpp | 256 ------------------ DCCpp_Uno/Outputs.h | 39 --- DCCpp_Uno/PacketRegister.cpp | 476 --------------------------------- DCCpp_Uno/PacketRegister.h | 64 ----- DCCpp_Uno/Sensor.cpp | 52 ++-- DCCpp_Uno/SerialCommand.cpp | 494 ++++------------------------------- DCCpp_Uno/SerialCommand.h | 6 - 16 files changed, 135 insertions(+), 2210 deletions(-) delete mode 100644 DCCpp_Uno/Accessories.cpp delete mode 100644 DCCpp_Uno/Accessories.h delete mode 100644 DCCpp_Uno/Comm.h delete mode 100644 DCCpp_Uno/CurrentMonitor.cpp delete mode 100644 DCCpp_Uno/CurrentMonitor.h delete mode 100644 DCCpp_Uno/Outputs.cpp delete mode 100644 DCCpp_Uno/Outputs.h delete mode 100644 DCCpp_Uno/PacketRegister.cpp delete mode 100644 DCCpp_Uno/PacketRegister.h diff --git a/DCCpp_Uno/Accessories.cpp b/DCCpp_Uno/Accessories.cpp deleted file mode 100644 index b027508..0000000 --- a/DCCpp_Uno/Accessories.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/********************************************************************** - -Accessories.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ -/********************************************************************** - -DCC++ BASE STATION can keep track of the direction of any turnout that is controlled -by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories -connected in this fashion, can always be operated using the DCC BASE STATION Accessory command: - - - -However, this general command simply sends the appropriate DCC instruction packet to the main tracks -to operate connected accessories. It does not store or retain any information regarding the current -status of that accessory. - -To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically -invoke the required command as needed, first define/edit/delete such turnouts using the following -variations of the "T" command: - - : creates a new turnout ID, with specified ADDRESS and SUBADDRESS - if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS - returns: if successful and if unsuccessful (e.g. out of memory) - - : deletes definition of turnout ID - returns: if successful and if unsuccessful (e.g. ID does not exist) - - : lists all defined turnouts - returns: for each defined turnout or if no turnouts defined - -where - - ID: the numeric ID (0-32767) of the turnout to control - ADDRESS: the primary address of the decoder controlling this turnout (0-511) - SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3) - -Once all turnouts have been properly defined, use the command to store their definitions to EEPROM. -If you later make edits/additions/deletions to the turnout definitions, you must invoke the command if you want those -new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. - -To "throw" turnouts that have been defined use: - - : sets turnout ID to either the "thrown" or "unthrown" position - returns: , or if turnout ID does not exist - -where - - ID: the numeric ID (0-32767) of the turnout to control - THROW: 0 (unthrown) or 1 (thrown) - -When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so -that it is retained even without power. A list of the current directions of each Turnout in the form is generated -by this sketch whenever the status command is invoked. This provides an efficient way of initializing -the directions of any Turnouts being monitored or controlled by a separate interface or GUI program. - -**********************************************************************/ - -#include "Accessories.h" -#include "SerialCommand.h" -#include "DCCpp_Uno.h" -#include "EEStore.h" -#include -#include "Comm.h" - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::activate(int s){ - char c[20]; - data.tStatus=(s>0); // if s>0 set turnout=ON, else if zero or negative set turnout=OFF - sprintf(c,"a %d %d %d",data.address,data.subAddress,data.tStatus); - SerialCommand::parse(c); - if(num>0) - EEPROM.put(num,data.tStatus); - INTERFACE.print(""); - else - INTERFACE.print(" 1>"); -} - -/////////////////////////////////////////////////////////////////////////////// - -Turnout* Turnout::get(int n){ - Turnout *tt; - for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout); - return(tt); -} -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::remove(int n){ - Turnout *tt,*pp; - - for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout); - - if(tt==NULL){ - INTERFACE.print(""); - return; - } - - if(tt==firstTurnout) - firstTurnout=tt->nextTurnout; - else - pp->nextTurnout=tt->nextTurnout; - - free(tt); - - INTERFACE.print(""); -} - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::show(int n){ - Turnout *tt; - - if(firstTurnout==NULL){ - INTERFACE.print(""); - return; - } - - for(tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){ - INTERFACE.print("data.id); - if(n==1){ - INTERFACE.print(" "); - INTERFACE.print(tt->data.address); - INTERFACE.print(" "); - INTERFACE.print(tt->data.subAddress); - } - if(tt->data.tStatus==0) - INTERFACE.print(" 0>"); - else - INTERFACE.print(" 1>"); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::parse(char *c){ - int n,s,m; - Turnout *t; - - switch(sscanf(c,"%d %d %d",&n,&s,&m)){ - - case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown) - t=get(n); - if(t!=NULL) - t->activate(s); - else - INTERFACE.print(""); - break; - - case 3: // argument is string with id number of turnout followed by an address and subAddress - create(n,s,m,1); - break; - - case 1: // argument is a string with id number only - remove(n); - break; - - case -1: // no arguments - show(1); // verbose show - break; - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::load(){ - struct TurnoutData data; - Turnout *tt; - - for(int i=0;idata.nTurnouts;i++){ - EEPROM.get(EEStore::pointer(),data); - tt=create(data.id,data.address,data.subAddress); - tt->data.tStatus=data.tStatus; - tt->num=EEStore::pointer(); - EEStore::advance(sizeof(tt->data)); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::store(){ - Turnout *tt; - - tt=firstTurnout; - EEStore::eeStore->data.nTurnouts=0; - - while(tt!=NULL){ - tt->num=EEStore::pointer(); - EEPROM.put(EEStore::pointer(),tt->data); - EEStore::advance(sizeof(tt->data)); - tt=tt->nextTurnout; - EEStore::eeStore->data.nTurnouts++; - } - -} -/////////////////////////////////////////////////////////////////////////////// - -Turnout *Turnout::create(int id, int add, int subAdd, int v){ - Turnout *tt; - - if(firstTurnout==NULL){ - firstTurnout=(Turnout *)calloc(1,sizeof(Turnout)); - tt=firstTurnout; - } else if((tt=get(id))==NULL){ - tt=firstTurnout; - while(tt->nextTurnout!=NULL) - tt=tt->nextTurnout; - tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout)); - tt=tt->nextTurnout; - } - - if(tt==NULL){ // problem allocating memory - if(v==1) - INTERFACE.print(""); - return(tt); - } - - tt->data.id=id; - tt->data.address=add; - tt->data.subAddress=subAdd; - tt->data.tStatus=0; - if(v==1) - INTERFACE.print(""); - return(tt); - -} - -/////////////////////////////////////////////////////////////////////////////// - -Turnout *Turnout::firstTurnout=NULL; - - diff --git a/DCCpp_Uno/Accessories.h b/DCCpp_Uno/Accessories.h deleted file mode 100644 index bbb9246..0000000 --- a/DCCpp_Uno/Accessories.h +++ /dev/null @@ -1,39 +0,0 @@ -/********************************************************************** - -Accessories.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include "Arduino.h" - -#ifndef Accessories_h -#define Accessories_h - -struct TurnoutData { - byte tStatus; - byte subAddress; - int id; - int address; -}; - -struct Turnout{ - static Turnout *firstTurnout; - int num; - struct TurnoutData data; - Turnout *nextTurnout; - void activate(int s); - static void parse(char *c); - static Turnout* get(int); - static void remove(int); - static void load(); - static void store(); - static Turnout *create(int, int, int, int=0); - static void show(int=0); -}; // Turnout - -#endif - - diff --git a/DCCpp_Uno/Comm.h b/DCCpp_Uno/Comm.h deleted file mode 100644 index d45a90e..0000000 --- a/DCCpp_Uno/Comm.h +++ /dev/null @@ -1,35 +0,0 @@ - - -/********************************************************************** - -Comm.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include "Config.h" - -#if COMM_TYPE == 1 // Ethernet Shield Card Selected - - #if COMM_INTERFACE == 1 - #define COMM_SHIELD_NAME "ARDUINO-CC ETHERNET SHIELD (WIZNET 5100)" - #include // built-in Arduino.cc library - - #elif COMM_INTERFACE == 2 - #define COMM_SHIELD_NAME "ARDUINO-ORG ETHERNET-2 SHIELD (WIZNET 5500)" - #include // https://github.com/arduino-org/Arduino - - #elif COMM_INTERFACE == 3 - #define COMM_SHIELD_NAME "SEEED STUDIO ETHERNET SHIELD (WIZNET 5200)" - #include // https://github.com/Seeed-Studio/Ethernet_Shield_W5200 - - #endif - - extern EthernetServer INTERFACE; -#endif - - - - diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 126f909..98b40a2 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -7,39 +7,6 @@ Part of DCC++ BASE STATION for the Arduino **********************************************************************/ -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE: -// -// 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) -// 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL) - -#define MOTOR_SHIELD_TYPE 0 - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE NUMBER OF MAIN TRACK REGISTER - -#define MAX_MAIN_REGISTERS 12 - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE COMMUNICATIONS INTERFACE -// -// 0 = Built-in Serial Port -// 1 = Arduino.cc Ethernet/SD-Card Shield -// 2 = Arduino.org Ethernet/SD-Card Shield -// 3 = Seeed Studio Ethernet/SD-Card Shield W5200 - -#define COMM_INTERFACE 0 - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP -// - -//#define IP_ADDRESS { 192, 168, 1, 200 } - ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE @@ -49,10 +16,10 @@ Part of DCC++ BASE STATION for the Arduino ///////////////////////////////////////////////////////////////////////////////////// // -// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// DEFINE WIFI PARAMETERS // - -#define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } - ///////////////////////////////////////////////////////////////////////////////////// +#define _SSID "" +#define _PASSWORD "" +#define _HOSTNAME "block01" diff --git a/DCCpp_Uno/CurrentMonitor.cpp b/DCCpp_Uno/CurrentMonitor.cpp deleted file mode 100644 index 5149d2a..0000000 --- a/DCCpp_Uno/CurrentMonitor.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/********************************************************************** - -CurrentMonitor.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include "DCCpp_Uno.h" -#include "CurrentMonitor.h" -#include "Comm.h" - -/////////////////////////////////////////////////////////////////////////////// - -CurrentMonitor::CurrentMonitor(int pin, char *msg){ - this->pin=pin; - this->msg=msg; - current=0; - } // CurrentMonitor::CurrentMonitor - -boolean CurrentMonitor::checkTime(){ - if(millis()-sampleTimeCURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){ // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together) - digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); // disable both Motor Shield Channels - digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); // regardless of which caused current overload - INTERFACE.print(msg); // print corresponding error message - } -} // CurrentMonitor::check - -long int CurrentMonitor::sampleTime=0; - diff --git a/DCCpp_Uno/CurrentMonitor.h b/DCCpp_Uno/CurrentMonitor.h deleted file mode 100644 index f4c301a..0000000 --- a/DCCpp_Uno/CurrentMonitor.h +++ /dev/null @@ -1,35 +0,0 @@ -/********************************************************************** - -CurrentMonitor.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#ifndef CurrentMonitor_h -#define CurrentMonitor_h - -#include "Arduino.h" - -#define CURRENT_SAMPLE_SMOOTHING 0.01 -#define CURRENT_SAMPLE_MAX 300 - -#ifdef ARDUINO_AVR_UNO // Configuration for UNO - #define CURRENT_SAMPLE_TIME 10 -#else // Configuration for MEGA - #define CURRENT_SAMPLE_TIME 1 -#endif - -struct CurrentMonitor{ - static long int sampleTime; - int pin; - float current; - char *msg; - CurrentMonitor(int, char *); - static boolean checkTime(); - void check(); -}; - -#endif - diff --git a/DCCpp_Uno/DCCpp_Uno.h b/DCCpp_Uno/DCCpp_Uno.h index d0f5f8b..8e2914e 100644 --- a/DCCpp_Uno/DCCpp_Uno.h +++ b/DCCpp_Uno/DCCpp_Uno.h @@ -7,8 +7,6 @@ Part of DCC++ BASE STATION for the Arduino **********************************************************************/ -#include "Config.h" - #ifndef DCCpp_Uno_h #define DCCpp_Uno_h @@ -16,119 +14,8 @@ Part of DCC++ BASE STATION for the Arduino // RELEASE VERSION ///////////////////////////////////////////////////////////////////////////////////// -#define VERSION "1.2.1+" - -///////////////////////////////////////////////////////////////////////////////////// -// AUTO-SELECT ARDUINO BOARD -///////////////////////////////////////////////////////////////////////////////////// - -#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" - - #define DCC_SIGNAL_PIN_MAIN 10 // Ardunio Uno - uses OC1B - #define DCC_SIGNAL_PIN_PROG 5 // Arduino Uno - uses OC0B - - #if COMM_INTERFACE != 0 // Serial was not selected - - #error CANNOT COMPILE - DCC++ FOR THE UNO CAN ONLY USE SERIAL COMMUNICATION - PLEASE SELECT THIS IN THE CONFIG FILE - - #endif - -#elif defined ARDUINO_AVR_MEGA2560 - - #define ARDUINO_TYPE "MEGA" - - #define DCC_SIGNAL_PIN_MAIN 12 // Arduino Mega - uses OC1B - #define DCC_SIGNAL_PIN_PROG 2 // Arduino Mega - uses OC3B - -#else - - #error CANNOT COMPILE - DCC++ ONLY WORKS WITH AN ARDUINO UNO OR AN ARDUINO MEGA 1280/2560 - -#endif - -///////////////////////////////////////////////////////////////////////////////////// -// SELECT MOTOR SHIELD -///////////////////////////////////////////////////////////////////////////////////// - -#if MOTOR_SHIELD_TYPE == 0 - - #define MOTOR_SHIELD_NAME "ARDUINO MOTOR SHIELD" - - #define SIGNAL_ENABLE_PIN_MAIN 3 - #define SIGNAL_ENABLE_PIN_PROG 11 - - #define CURRENT_MONITOR_PIN_MAIN A0 - #define CURRENT_MONITOR_PIN_PROG A1 - - #define DIRECTION_MOTOR_CHANNEL_PIN_A 12 - #define DIRECTION_MOTOR_CHANNEL_PIN_B 13 - -#elif MOTOR_SHIELD_TYPE == 1 - - #define MOTOR_SHIELD_NAME "POLOLU MC33926 MOTOR SHIELD" - - #define SIGNAL_ENABLE_PIN_MAIN 9 - #define SIGNAL_ENABLE_PIN_PROG 11 - - #define CURRENT_MONITOR_PIN_MAIN A0 - #define CURRENT_MONITOR_PIN_PROG A1 - - #define DIRECTION_MOTOR_CHANNEL_PIN_A 7 - #define DIRECTION_MOTOR_CHANNEL_PIN_B 8 - -#else - - #error CANNOT COMPILE - PLEASE SELECT A PROPER MOTOR SHIELD TYPE - -#endif - -///////////////////////////////////////////////////////////////////////////////////// -// SELECT COMMUNICATION INTERACE -///////////////////////////////////////////////////////////////////////////////////// - -#if COMM_INTERFACE == 0 - - #define COMM_TYPE 0 - #define INTERFACE Serial - -#elif (COMM_INTERFACE==1) || (COMM_INTERFACE==2) || (COMM_INTERFACE==3) - - #define COMM_TYPE 1 - #define INTERFACE eServer - #define SDCARD_CS 4 - -#else - - #error CANNOT COMPILE - Please select a proper value for COMM_INTERFACE in CONFIG.H file - -#endif - -///////////////////////////////////////////////////////////////////////////////////// -// SET WHETHER TO SHOW PACKETS - DIAGNOSTIC MODE ONLY -///////////////////////////////////////////////////////////////////////////////////// - -// If SHOW_PACKETS is set to 1, then for select main operations track commands that modify an internal DCC packet register, -// if printFlag for that command is also set to 1, DCC++ BASE STATION will additionally return the -// DCC packet contents of the modified register in the following format: - -// <* REG: B1 B2 ... Bn CSUM / REPEAT> -// -// REG: the number of the main operations track packet register that was modified -// B1: the first hexidecimal byte of the DCC packet -// B2: the second hexidecimal byte of the DCC packet -// Bn: the nth hexidecimal byte of the DCC packet -// CSUM: a checksum byte that is required to be the final byte in any DCC packet -// REPEAT: the number of times the DCC packet was re-transmitted to the tracks after its iniital transmission - -#define SHOW_PACKETS 0 // set to zero to disable printing of every packet for select main operations track commands - -///////////////////////////////////////////////////////////////////////////////////// +#define VERSION "1.2.1++" +#define ARDUINO_TYPE "NodeMCU" #endif diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index 9378374..50c736e 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -167,34 +167,47 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use **********************************************************************/ // BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE - + +#include +#include "Config.h" #include "DCCpp_Uno.h" -#include "PacketRegister.h" -#include "CurrentMonitor.h" #include "Sensor.h" +#include "EEstore.h" #include "SerialCommand.h" -#include "Accessories.h" -#include "EEStore.h" -#include "Config.h" -#include "Comm.h" - -void showConfiguration(); - -// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE - -#if COMM_TYPE == 1 - byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server) - EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer -#endif -// NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS. -// NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES - -volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets -volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets +WiFiServer server(ETHERNET_PORT); +WiFiClient client; + +void connectToWiFi() +{ + Serial.print("\n\nConnecting to "); + Serial.println(_SSID); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + WiFi.begin( _SSID, _PASSWORD ); + WiFi.hostname( _HOSTNAME ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + WiFi.disconnect(); + + Serial.println( WiFi.softAP( _HOSTNAME, _PASSWORD, 6) ? "AP Started": "AP failed" ); + Serial.print("\n\nConnecting to "); + Serial.println( _SSID ); + WiFi.mode(WIFI_AP); + WiFi.begin( _SSID, _PASSWORD ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + server.begin(); +} -CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track -CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP @@ -203,12 +216,6 @@ CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor void loop(){ SerialCommand::process(); // check for, and process, and new serial commands - - if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks - mainMonitor.check(); - progMonitor.check(); - } - Sensor::check(); // check sensors for activate/de-activate } // loop @@ -222,23 +229,11 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); - #ifdef SDCARD_CS - pinMode(SDCARD_CS,OUTPUT); - digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card - #endif - EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM - pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt - digitalWrite(A5,HIGH); - if(!digitalRead(A5)) - showConfiguration(); - - Serial.print(""); - #if COMM_TYPE == 1 - #ifdef IP_ADDRESS - Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address - #else - Ethernet.begin(mac); // Start networking using DHCP to get an IP Address - #endif - INTERFACE.begin(); - #endif - - SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line - Serial.print(""); - #elif COMM_TYPE == 1 - Serial.print(Ethernet.localIP()); - Serial.print(">"); - #endif - - // CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS - - // Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK - // Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin - // Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency - // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle - - #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199 - #define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599 - - #define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855 - #define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927 - - pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) - digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW); - - pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A - - bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A - bitSet(TCCR1A,WGM11); - bitSet(TCCR1B,WGM12); - bitSet(TCCR1B,WGM13); - - bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary) - bitSet(TCCR1A,COM1B0); - - bitClear(TCCR1B,CS12); // set Timer 1 prescale=1 - bitClear(TCCR1B,CS11); - bitSet(TCCR1B,CS10); - - OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1; - OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1; - - pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A - - mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 - - bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B) - - // CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS - -#ifdef ARDUINO_AVR_UNO // Configuration for UNO - - // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK - // Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin - // Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency - // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with as-close-as-possible to 50% duty cycle - - #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49 - #define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24 - - #define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28 - #define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14 - - pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) - digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); - - pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B - - bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A - bitSet(TCCR0A,WGM01); - bitSet(TCCR0B,WGM02); - - bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary) - bitSet(TCCR0A,COM0B0); - - bitClear(TCCR0B,CS02); // set Timer 0 prescale=64 - bitSet(TCCR0B,CS01); - bitSet(TCCR0B,CS00); - - OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0; - OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0; - - pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B - - progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 - - bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B) - -#else // Configuration for MEGA - - // Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK - // Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin - // Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency - // Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle - - #define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199 - #define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599 - - #define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855 - #define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927 - - pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below) - digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW); - - pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B - - bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A - bitSet(TCCR3A,WGM31); - bitSet(TCCR3B,WGM32); - bitSet(TCCR3B,WGM33); - - bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary) - bitSet(TCCR3A,COM3B0); - - bitClear(TCCR3B,CS32); // set Timer 3 prescale=1 - bitClear(TCCR3B,CS31); - bitSet(TCCR3B,CS30); - - OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3; - OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3; - - pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B - - progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1 - - bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B) - -#endif - + connectToWiFi(); + + Serial.print(WiFi.localIP()); + Serial.print(">"); } // setup - -/////////////////////////////////////////////////////////////////////////////// -// DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL -/////////////////////////////////////////////////////////////////////////////// - -// The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1. -// It is designed to read the current bit of the current register packet and -// updates the OCNA and OCNB counters of Timer-N to values that will either produce -// a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent -// DCC ZERO and DCC ONE bits. - -// These are hardware-driven interrupts that will be called automatically when triggered regardless of what -// DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled. -// Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts -// (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete -// in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic -// of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB -// registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the -// interrupt code completes and can be called again. - -// A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed -// DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible. - -// Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the -// Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro -// that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating -// some of the logic for each interrupt, but saves additional time. - -// As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds -// when a new register is loaded and the logic needs to switch active register packet pointers. - -// THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1) - -#define DCC_SIGNAL(R,N) \ - if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \ - R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \ - if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \ - R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \ - } else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \ - R.currentReg=R.nextReg; /* update currentReg to nextReg */ \ - R.nextReg=NULL; /* reset nextReg to NULL */ \ - R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \ - R.currentReg->activePacket=R.currentReg->updatePacket; \ - R.currentReg->updatePacket=R.tempPacket; \ - } else{ /* ELSE simply move to next Register */ \ - if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \ - R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \ - R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \ - } /* END-ELSE */ \ - } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \ - \ - if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \ - OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \ - OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \ - } else{ /* ELSE it is a ZERO */ \ - OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \ - OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \ - } /* END-ELSE */ \ - \ - R.currentBit++; /* point to next bit in current Packet */ - -/////////////////////////////////////////////////////////////////////////////// - -// NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT - -ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track - DCC_SIGNAL(mainRegs,1) -} - -#ifdef ARDUINO_AVR_UNO // Configuration for UNO - -ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track - DCC_SIGNAL(progRegs,0) -} - -#else // Configuration for MEGA - -ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track - DCC_SIGNAL(progRegs,3) -} - -#endif - - -/////////////////////////////////////////////////////////////////////////////// -// PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE -// - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH - -void showConfiguration(){ - - int mac_address[]=MAC_ADDRESS; - - Serial.print("\n*** DCC++ CONFIGURATION ***\n"); - - Serial.print("\nVERSION: "); - Serial.print(VERSION); - Serial.print("\nCOMPILED: "); - Serial.print(__DATE__); - Serial.print(" "); - Serial.print(__TIME__); - - Serial.print("\nARDUINO: "); - Serial.print(ARDUINO_TYPE); - - Serial.print("\n\nMOTOR SHIELD: "); - Serial.print(MOTOR_SHIELD_NAME); - - Serial.print("\n\nDCC SIG MAIN: "); - Serial.print(DCC_SIGNAL_PIN_MAIN); - Serial.print("\n DIRECTION: "); - Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A); - Serial.print("\n ENABLE: "); - Serial.print(SIGNAL_ENABLE_PIN_MAIN); - Serial.print("\n CURRENT: "); - Serial.print(CURRENT_MONITOR_PIN_MAIN); - - Serial.print("\n\nDCC SIG PROG: "); - Serial.print(DCC_SIGNAL_PIN_PROG); - Serial.print("\n DIRECTION: "); - Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B); - Serial.print("\n ENABLE: "); - Serial.print(SIGNAL_ENABLE_PIN_PROG); - Serial.print("\n CURRENT: "); - Serial.print(CURRENT_MONITOR_PIN_PROG); - - Serial.print("\n\nNUM TURNOUTS: "); - Serial.print(EEStore::eeStore->data.nTurnouts); - Serial.print("\n SENSORS: "); - Serial.print(EEStore::eeStore->data.nSensors); - Serial.print("\n OUTPUTS: "); - Serial.print(EEStore::eeStore->data.nOutputs); - - Serial.print("\n\nINTERFACE: "); - #if COMM_TYPE == 0 - Serial.print("SERIAL"); - #elif COMM_TYPE == 1 - Serial.print(COMM_SHIELD_NAME); - Serial.print("\nMAC ADDRESS: "); - for(int i=0;i<5;i++){ - Serial.print(mac_address[i],HEX); - Serial.print(":"); - } - Serial.print(mac_address[5],HEX); - Serial.print("\nPORT: "); - Serial.print(ETHERNET_PORT); - Serial.print("\nIP ADDRESS: "); - - #ifdef IP_ADDRESS - Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address - #else - Ethernet.begin(mac); // Start networking using DHCP to get an IP Address - #endif - - Serial.print(Ethernet.localIP()); - - #ifdef IP_ADDRESS - Serial.print(" (STATIC)"); - #else - Serial.print(" (DHCP)"); - #endif - - #endif - Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO"); - - while(true); -} - -/////////////////////////////////////////////////////////////////////////////// - - - - diff --git a/DCCpp_Uno/EEStore.cpp b/DCCpp_Uno/EEStore.cpp index a58d431..01b6488 100644 --- a/DCCpp_Uno/EEStore.cpp +++ b/DCCpp_Uno/EEStore.cpp @@ -9,9 +9,7 @@ Part of DCC++ BASE STATION for the Arduino #include "DCCpp_Uno.h" #include "EEStore.h" -#include "Accessories.h" #include "Sensor.h" -#include "Outputs.h" #include /////////////////////////////////////////////////////////////////////////////// @@ -21,6 +19,7 @@ void EEStore::init(){ eeStore=(EEStore *)calloc(1,sizeof(EEStore)); + EEPROM.begin( 4096 ); EEPROM.get(0,eeStore->data); // get eeStore data if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID @@ -32,21 +31,21 @@ void EEStore::init(){ } reset(); // set memory pointer to first free EEPROM space - Turnout::load(); // load turnout definitions Sensor::load(); // load sensor definitions - Output::load(); // load output definitions - } /////////////////////////////////////////////////////////////////////////////// void EEStore::clear(){ - + + EEPROM.begin( 4096 ); sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM eeStore->data.nTurnouts=0; eeStore->data.nSensors=0; eeStore->data.nOutputs=0; EEPROM.put(0,eeStore->data); + EEPROM.commit(); + EEPROM.end(); } @@ -54,10 +53,11 @@ void EEStore::clear(){ void EEStore::store(){ reset(); - Turnout::store(); + EEPROM.begin( 4096 ); Sensor::store(); - Output::store(); - EEPROM.put(0,eeStore->data); + EEPROM.put(0,eeStore->data); + EEPROM.commit(); + EEPROM.end(); } /////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/Outputs.cpp b/DCCpp_Uno/Outputs.cpp deleted file mode 100644 index fe2ddef..0000000 --- a/DCCpp_Uno/Outputs.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/********************************************************************** - -Outputs.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ -/********************************************************************** - -DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes. -Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW. -However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH. - -Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up. -The default is to set each defined pin to active or inactive according to its restored state. -However, the default behavior can be modified so that any pin can be forced to be either active or inactive -upon power-up regardless of its previous state before power-down. - -To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete -output definitions using the following variation of the "Z" command: - - : creates a new output ID, with specified PIN and IFLAG values. - if output ID already exists, it is updated with specificed PIN and IFLAG. - note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW - according to IFLAG value specifcied (see below). - returns: if successful and if unsuccessful (e.g. out of memory) - - : deletes definition of output ID - returns: if successful and if unsuccessful (e.g. ID does not exist) - - : lists all defined output pins - returns: for each defined output pin or if no output pins defined - -where - - ID: the numeric ID (0-32767) of the output - PIN: the arduino pin number to use for the output - STATE: the state of the output (0=INACTIVE / 1=ACTIVE) - IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows: - - IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW) - 1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH) - - IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending - on state before power-down; state of pin set to INACTIVE when first created - 1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE - depending on IFLAG, bit 2 - - IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created - 1 = state of pin set to ACTIVE uponm power-up or when first created - -Once all outputs have been properly defined, use the command to store their definitions to EEPROM. -If you later make edits/additions/deletions to the output definitions, you must invoke the command if you want those -new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. - -To change the state of outputs that have been defined use: - - : sets output ID to either ACTIVE or INACTIVE state - returns: , or if turnout ID does not exist - -where - - ID: the numeric ID (0-32767) of the turnout to control - STATE: the state of the output (0=INACTIVE / 1=ACTIVE) - -When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so -that it is retained even without power. A list of the current states of each output in the form is generated -by this sketch whenever the status command is invoked. This provides an efficient way of initializing -the state of any outputs being monitored or controlled by a separate interface or GUI program. - -**********************************************************************/ - -#include "Outputs.h" -#include "SerialCommand.h" -#include "DCCpp_Uno.h" -#include "EEStore.h" -#include -#include "Comm.h" - -/////////////////////////////////////////////////////////////////////////////// - -void Output::activate(int s){ - data.oStatus=(s>0); // if s>0, set status to active, else inactive - digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) - if(num>0) - EEPROM.put(num,data.oStatus); - INTERFACE.print(""); - else - INTERFACE.print(" 1>"); -} - -/////////////////////////////////////////////////////////////////////////////// - -Output* Output::get(int n){ - Output *tt; - for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput); - return(tt); -} -/////////////////////////////////////////////////////////////////////////////// - -void Output::remove(int n){ - Output *tt,*pp; - - for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput); - - if(tt==NULL){ - INTERFACE.print(""); - return; - } - - if(tt==firstOutput) - firstOutput=tt->nextOutput; - else - pp->nextOutput=tt->nextOutput; - - free(tt); - - INTERFACE.print(""); -} - -/////////////////////////////////////////////////////////////////////////////// - -void Output::show(int n){ - Output *tt; - - if(firstOutput==NULL){ - INTERFACE.print(""); - return; - } - - for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ - INTERFACE.print("data.id); - if(n==1){ - INTERFACE.print(" "); - INTERFACE.print(tt->data.pin); - INTERFACE.print(" "); - INTERFACE.print(tt->data.iFlag); - } - if(tt->data.oStatus==0) - INTERFACE.print(" 0>"); - else - INTERFACE.print(" 1>"); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Output::parse(char *c){ - int n,s,m; - Output *t; - - switch(sscanf(c,"%d %d %d",&n,&s,&m)){ - - case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) - t=get(n); - if(t!=NULL) - t->activate(s); - else - INTERFACE.print(""); - break; - - case 3: // argument is string with id number of output followed by a pin number and invert flag - create(n,s,m,1); - break; - - case 1: // argument is a string with id number only - remove(n); - break; - - case -1: // no arguments - show(1); // verbose show - break; - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Output::load(){ - struct OutputData data; - Output *tt; - - for(int i=0;idata.nOutputs;i++){ - EEPROM.get(EEStore::pointer(),data); - tt=create(data.id,data.pin,data.iFlag); - tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag - digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); - pinMode(tt->data.pin,OUTPUT); - tt->num=EEStore::pointer(); - EEStore::advance(sizeof(tt->data)); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Output::store(){ - Output *tt; - - tt=firstOutput; - EEStore::eeStore->data.nOutputs=0; - - while(tt!=NULL){ - tt->num=EEStore::pointer(); - EEPROM.put(EEStore::pointer(),tt->data); - EEStore::advance(sizeof(tt->data)); - tt=tt->nextOutput; - EEStore::eeStore->data.nOutputs++; - } - -} -/////////////////////////////////////////////////////////////////////////////// - -Output *Output::create(int id, int pin, int iFlag, int v){ - Output *tt; - - if(firstOutput==NULL){ - firstOutput=(Output *)calloc(1,sizeof(Output)); - tt=firstOutput; - } else if((tt=get(id))==NULL){ - tt=firstOutput; - while(tt->nextOutput!=NULL) - tt=tt->nextOutput; - tt->nextOutput=(Output *)calloc(1,sizeof(Output)); - tt=tt->nextOutput; - } - - if(tt==NULL){ // problem allocating memory - if(v==1) - INTERFACE.print(""); - return(tt); - } - - tt->data.id=id; - tt->data.pin=pin; - tt->data.iFlag=iFlag; - tt->data.oStatus=0; - - if(v==1){ - tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag - digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); - pinMode(tt->data.pin,OUTPUT); - INTERFACE.print(""); - } - - return(tt); - -} - -/////////////////////////////////////////////////////////////////////////////// - -Output *Output::firstOutput=NULL; - diff --git a/DCCpp_Uno/Outputs.h b/DCCpp_Uno/Outputs.h deleted file mode 100644 index 1748bce..0000000 --- a/DCCpp_Uno/Outputs.h +++ /dev/null @@ -1,39 +0,0 @@ -/********************************************************************** - -Outputs.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include "Arduino.h" - -#ifndef Outputs_h -#define Outputs_h - -struct OutputData { - byte oStatus; - int id; - byte pin; - byte iFlag; -}; - -struct Output{ - static Output *firstOutput; - int num; - struct OutputData data; - Output *nextOutput; - void activate(int s); - static void parse(char *c); - static Output* get(int); - static void remove(int); - static void load(); - static void store(); - static Output *create(int, int, int, int=0); - static void show(int=0); -}; // Output - -#endif - - diff --git a/DCCpp_Uno/PacketRegister.cpp b/DCCpp_Uno/PacketRegister.cpp deleted file mode 100644 index 2d69d60..0000000 --- a/DCCpp_Uno/PacketRegister.cpp +++ /dev/null @@ -1,476 +0,0 @@ -/********************************************************************** - -PacketRegister.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include "DCCpp_Uno.h" -#include "PacketRegister.h" -#include "Comm.h" - -/////////////////////////////////////////////////////////////////////////////// - -void Register::initPackets(){ - activePacket=packet; - updatePacket=packet+1; -} // Register::initPackets - -/////////////////////////////////////////////////////////////////////////////// - -RegisterList::RegisterList(int maxNumRegs){ - this->maxNumRegs=maxNumRegs; - reg=(Register *)calloc((maxNumRegs+1),sizeof(Register)); - for(int i=0;i<=maxNumRegs;i++) - reg[i].initPackets(); - regMap=(Register **)calloc((maxNumRegs+1),sizeof(Register *)); - speedTable=(int *)calloc((maxNumRegs+1),sizeof(int *)); - currentReg=reg; - regMap[0]=reg; - maxLoadedReg=reg; - nextReg=NULL; - currentBit=0; - nRepeat=0; -} // RegisterList::RegisterList - -/////////////////////////////////////////////////////////////////////////////// - -// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE) -// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS -// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS) - -void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile { - - nReg=nReg%((maxNumRegs+1)); // force nReg to be between 0 and maxNumRegs, inclusive - - while(nextReg!=NULL); // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed - - if(regMap[nReg]==NULL) // first time this Register Number has been called - regMap[nReg]=maxLoadedReg+1; // set Register Pointer for this Register Number to next available Register - - Register *r=regMap[nReg]; // set Register to be updated - Packet *p=r->updatePacket; // set Packet in the Register to be updated - byte *buf=p->buf; // set byte buffer in the Packet to be updated - - b[nBytes]=b[0]; // copy first byte into what will become the checksum byte - for(int i=1;i>1; // b[2], bits 7-1 - buf[6]=b[2]<<7; // b[2], bit 0 - - if(nBytes==3){ - p->nBits=49; - } else{ - buf[6]+=b[3]>>2; // b[3], bits 7-2 - buf[7]=b[3]<<6; // b[3], bit 1-0 - if(nBytes==4){ - p->nBits=58; - } else{ - buf[7]+=b[4]>>3; // b[4], bits 7-3 - buf[8]=b[4]<<5; // b[4], bits 2-0 - if(nBytes==5){ - p->nBits=67; - } else{ - buf[8]+=b[5]>>4; // b[5], bits 7-4 - buf[9]=b[5]<<4; // b[5], bits 3-0 - p->nBits=76; - } // >5 bytes - } // >4 bytes - } // >3 bytes - - nextReg=r; - this->nRepeat=nRepeat; - maxLoadedReg=max(maxLoadedReg,nextReg); - - if(printFlag && SHOW_PACKETS) // for debugging purposes - printPacket(nReg,b,nBytes,nRepeat); - -} // RegisterList::loadPacket - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::setThrottle(char *s) volatile{ - byte b[5]; // save space for checksum byte - int nReg; - int cab; - int tSpeed; - int tDirection; - byte nB=0; - - if(sscanf(s,"%d %d %d %d",&nReg,&cab,&tSpeed,&tDirection)!=4) - return; - - if(nReg<1 || nReg>maxNumRegs) - return; - - if(cab>127) - b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address - - b[nB++]=lowByte(cab); - b[nB++]=0x3F; // 128-step speed control byte - if(tSpeed>=0) - b[nB++]=tSpeed+(tSpeed>0)+tDirection*128; // max speed is 126, but speed codes range from 2-127 (0=stop, 1=emergency stop) - else{ - b[nB++]=1; - tSpeed=0; - } - - loadPacket(nReg,b,nB,0,1); - - INTERFACE.print(""); - - speedTable[nReg]=tDirection==1?tSpeed:-tSpeed; - -} // RegisterList::setThrottle() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::setFunction(char *s) volatile{ - byte b[5]; // save space for checksum byte - int cab; - int fByte, eByte; - int nParams; - byte nB=0; - - nParams=sscanf(s,"%d %d %d",&cab,&fByte,&eByte); - - if(nParams<2) - return; - - if(cab>127) - b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address - - b[nB++]=lowByte(cab); - - if(nParams==2){ // this is a request for functions FL,F1-F12 - b[nB++]=(fByte | 0x80) & 0xBF; // for safety this guarantees that first nibble of function byte will always be of binary form 10XX which should always be the case for FL,F1-F12 - } else { // this is a request for functions F13-F28 - b[nB++]=(fByte | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28) - b[nB++]=eByte; - } - - loadPacket(0,b,nB,4,1); - -} // RegisterList::setFunction() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::setAccessory(char *s) volatile{ - byte b[3]; // save space for checksum byte - int aAdd; // the accessory address (0-511 = 9 bits) - int aNum; // the accessory number within that address (0-3) - int activate; // flag indicated whether accessory should be activated (1) or deactivated (0) following NMRA recommended convention - - if(sscanf(s,"%d %d %d",&aAdd,&aNum,&activate)!=3) - return; - - b[0]=aAdd%64+128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address - b[1]=((((aAdd/64)%8)<<4) + (aNum%4<<1) + activate%2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate - - loadPacket(0,b,2,4,1); - -} // RegisterList::setAccessory() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::writeTextPacket(char *s) volatile{ - - int nReg; - byte b[6]; - int nBytes; - volatile RegisterList *regs; - - nBytes=sscanf(s,"%d %x %x %x %x %x",&nReg,b,b+1,b+2,b+3,b+4)-1; - - if(nBytes<2 || nBytes>5){ // invalid valid packet - INTERFACE.print(""); - return; - } - - loadPacket(nReg,b,nBytes,0,1); - -} // RegisterList::writeTextPacket() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::readCV(char *s) volatile{ - byte bRead[4]; - int bValue; - int c,d,base; - int cv, callBack, callBackSub; - - if(sscanf(s,"%d %d %d",&cv,&callBack,&callBackSub)!=3) // cv = 1-1024 - return; - cv--; // actual CV addresses are cv-1 (0-1023) - - bRead[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 - bRead[1]=lowByte(cv); - - bValue=0; - - for(int i=0;i<8;i++){ - - c=0; - d=0; - base=0; - - for(int j=0;jACK_SAMPLE_THRESHOLD) - d=1; - } - - bitWrite(bValue,i,d); - } - - c=0; - d=0; - base=0; - - for(int j=0;jACK_SAMPLE_THRESHOLD) - d=1; - } - - if(d==0) // verify unsuccessful - bValue=-1; - - INTERFACE.print(""); - -} // RegisterList::readCV() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::writeCVByte(char *s) volatile{ - byte bWrite[4]; - int bValue; - int c,d,base; - int cv, callBack, callBackSub; - - if(sscanf(s,"%d %d %d %d",&cv,&bValue,&callBack,&callBackSub)!=4) // cv = 1-1024 - return; - cv--; // actual CV addresses are cv-1 (0-1023) - - bWrite[0]=0x7C+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 - bWrite[1]=lowByte(cv); - bWrite[2]=bValue; - - loadPacket(0,resetPacket,2,1); - loadPacket(0,bWrite,3,4); - loadPacket(0,resetPacket,2,1); - loadPacket(0,idlePacket,2,10); - - c=0; - d=0; - base=0; - - for(int j=0;jACK_SAMPLE_THRESHOLD) - d=1; - } - - if(d==0) // verify unsuccessful - bValue=-1; - - INTERFACE.print(""); - -} // RegisterList::writeCVByte() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::writeCVBit(char *s) volatile{ - byte bWrite[4]; - int bNum,bValue; - int c,d,base; - int cv, callBack, callBackSub; - - if(sscanf(s,"%d %d %d %d %d",&cv,&bNum,&bValue,&callBack,&callBackSub)!=5) // cv = 1-1024 - return; - cv--; // actual CV addresses are cv-1 (0-1023) - bValue=bValue%2; - bNum=bNum%8; - - bWrite[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 - bWrite[1]=lowByte(cv); - bWrite[2]=0xF0+bValue*8+bNum; - - loadPacket(0,resetPacket,2,1); - loadPacket(0,bWrite,3,4); - loadPacket(0,resetPacket,2,1); - loadPacket(0,idlePacket,2,10); - - c=0; - d=0; - base=0; - - for(int j=0;jACK_SAMPLE_THRESHOLD) - d=1; - } - - if(d==0) // verify unsuccessful - bValue=-1; - - INTERFACE.print(""); - -} // RegisterList::writeCVBit() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::writeCVByteMain(char *s) volatile{ - byte b[6]; // save space for checksum byte - int cab; - int cv; - int bValue; - byte nB=0; - - if(sscanf(s,"%d %d %d",&cab,&cv,&bValue)!=3) - return; - cv--; - - if(cab>127) - b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address - - b[nB++]=lowByte(cab); - b[nB++]=0xEC+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 - b[nB++]=lowByte(cv); - b[nB++]=bValue; - - loadPacket(0,b,nB,4); - -} // RegisterList::writeCVByteMain() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::writeCVBitMain(char *s) volatile{ - byte b[6]; // save space for checksum byte - int cab; - int cv; - int bNum; - int bValue; - byte nB=0; - - if(sscanf(s,"%d %d %d %d",&cab,&cv,&bNum,&bValue)!=4) - return; - cv--; - - bValue=bValue%2; - bNum=bNum%8; - - if(cab>127) - b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address - - b[nB++]=lowByte(cab); - b[nB++]=0xE8+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03 - b[nB++]=lowByte(cv); - b[nB++]=0xF0+bValue*8+bNum; - - loadPacket(0,b,nB,4); - -} // RegisterList::writeCVBitMain() - -/////////////////////////////////////////////////////////////////////////////// - -void RegisterList::printPacket(int nReg, byte *b, int nBytes, int nRepeat) volatile { - - INTERFACE.print("<*"); - INTERFACE.print(nReg); - INTERFACE.print(":"); - for(int i=0;i"); -} // RegisterList::printPacket() - -/////////////////////////////////////////////////////////////////////////////// - -byte RegisterList::idlePacket[3]={0xFF,0x00,0}; // always leave extra byte for checksum computation -byte RegisterList::resetPacket[3]={0x00,0x00,0}; - -byte RegisterList::bitMask[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; // masks used in interrupt routine to speed the query of a single bit in a Packet diff --git a/DCCpp_Uno/PacketRegister.h b/DCCpp_Uno/PacketRegister.h deleted file mode 100644 index 7132fec..0000000 --- a/DCCpp_Uno/PacketRegister.h +++ /dev/null @@ -1,64 +0,0 @@ -/********************************************************************** - -PacketRegister.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#ifndef PacketRegister_h -#define PacketRegister_h - -#include "Arduino.h" - -// Define constants used for reading CVs from the Programming Track - -#define ACK_BASE_COUNT 100 // number of analogRead samples to take before each CV verify to establish a baseline current -#define ACK_SAMPLE_COUNT 500 // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent -#define ACK_SAMPLE_SMOOTHING 0.2 // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent -#define ACK_SAMPLE_THRESHOLD 30 // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT - -// Define a series of registers that can be sequentially accessed over a loop to generate a repeating series of DCC Packets - -struct Packet{ - byte buf[10]; - byte nBits; -}; // Packet - -struct Register{ - Packet packet[2]; - Packet *activePacket; - Packet *updatePacket; - void initPackets(); -}; // Register - -struct RegisterList{ - int maxNumRegs; - Register *reg; - Register **regMap; - Register *currentReg; - Register *maxLoadedReg; - Register *nextReg; - Packet *tempPacket; - byte currentBit; - byte nRepeat; - int *speedTable; - static byte idlePacket[]; - static byte resetPacket[]; - static byte bitMask[]; - RegisterList(int); - void loadPacket(int, byte *, int, int, int=0) volatile; - void setThrottle(char *) volatile; - void setFunction(char *) volatile; - void setAccessory(char *) volatile; - void writeTextPacket(char *) volatile; - void readCV(char *) volatile; - void writeCVByte(char *) volatile; - void writeCVBit(char *) volatile; - void writeCVByteMain(char *) volatile; - void writeCVBitMain(char *s) volatile; - void printPacket(int, byte *, int, int) volatile; -}; - -#endif diff --git a/DCCpp_Uno/Sensor.cpp b/DCCpp_Uno/Sensor.cpp index 11fca36..af63ef1 100644 --- a/DCCpp_Uno/Sensor.cpp +++ b/DCCpp_Uno/Sensor.cpp @@ -54,12 +54,14 @@ Depending on whether the physical sensor is acting as an "event-trigger" or a "d decide to ignore the return and only react to triggers. **********************************************************************/ - +#include #include "DCCpp_Uno.h" #include "Sensor.h" #include "EEStore.h" #include -#include "Comm.h" + +extern WiFiClient client; +extern WiFiServer server; /////////////////////////////////////////////////////////////////////////////// @@ -71,14 +73,14 @@ void Sensor::check(){ if(!tt->active && tt->signal<0.5){ tt->active=true; - INTERFACE.print("data.snum); - INTERFACE.print(">"); + client.print("data.snum); + client.print(">"); } else if(tt->active && tt->signal>0.9){ tt->active=false; - INTERFACE.print("data.snum); - INTERFACE.print(">"); + client.print("data.snum); + client.print(">"); } } // loop over all sensors @@ -102,7 +104,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ if(tt==NULL){ // problem allocating memory if(v==1) - INTERFACE.print(""); + client.print(""); return(tt); } @@ -115,7 +117,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor if(v==1) - INTERFACE.print(""); + client.print(""); return(tt); } @@ -135,7 +137,7 @@ void Sensor::remove(int n){ for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor); if(tt==NULL){ - INTERFACE.print(""); + client.print(""); return; } @@ -146,7 +148,7 @@ void Sensor::remove(int n){ free(tt); - INTERFACE.print(""); + client.print(""); } /////////////////////////////////////////////////////////////////////////////// @@ -155,18 +157,18 @@ void Sensor::show(){ Sensor *tt; if(firstSensor==NULL){ - INTERFACE.print(""); + client.print(""); return; } for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - INTERFACE.print("data.snum); - INTERFACE.print(" "); - INTERFACE.print(tt->data.pin); - INTERFACE.print(" "); - INTERFACE.print(tt->data.pullUp); - INTERFACE.print(">"); + client.print("data.snum); + client.print(" "); + client.print(tt->data.pin); + client.print(" "); + client.print(tt->data.pullUp); + client.print(">"); } } @@ -176,14 +178,14 @@ void Sensor::status(){ Sensor *tt; if(firstSensor==NULL){ - INTERFACE.print(""); + client.print(""); return; } for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - INTERFACE.print(tt->active?"data.snum); - INTERFACE.print(">"); + client.print(tt->active?"data.snum); + client.print(">"); } } @@ -208,7 +210,7 @@ void Sensor::parse(char *c){ break; case 2: // invalid number of arguments - INTERFACE.print(""); + client.print(""); break; } } diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index 4d3f9b2..5fb33bb 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -14,67 +14,44 @@ Part of DCC++ BASE STATION for the Arduino // See SerialCommand::parse() below for defined text commands. +#include #include "SerialCommand.h" #include "DCCpp_Uno.h" -#include "Accessories.h" #include "Sensor.h" -#include "Outputs.h" #include "EEStore.h" -#include "Comm.h" -extern int __heap_start, *__brkval; +extern WiFiClient client; +extern WiFiServer server; /////////////////////////////////////////////////////////////////////////////// char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; -volatile RegisterList *SerialCommand::mRegs; -volatile RegisterList *SerialCommand::pRegs; -CurrentMonitor *SerialCommand::mMonitor; - -/////////////////////////////////////////////////////////////////////////////// - -void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){ - mRegs=_mRegs; - pRegs=_pRegs; - mMonitor=_mMonitor; - sprintf(commandString,""); -} // SerialCommand:SerialCommand /////////////////////////////////////////////////////////////////////////////// void SerialCommand::process(){ char c; - #if COMM_TYPE == 0 - - while(INTERFACE.available()>0){ // while there is data on the serial line - c=INTERFACE.read(); - if(c=='<') // start of new command - sprintf(commandString,""); - else if(c=='>') // end of new command - parse(commandString); - else if(strlen(commandString)') - } // while - - #elif COMM_TYPE == 1 - - EthernetClient client=INTERFACE.available(); + client = server.available(); if(client){ - while(client.connected() && client.available()){ // while there is data on the network - c=client.read(); - if(c=='<') // start of new command - sprintf(commandString,""); - else if(c=='>') // end of new command - parse(commandString); - else if(strlen(commandString)') +// Serial.print("^" ); + while( client.connected() ) { + if( client.available()){ // while there is data on the network + c=client.read(); + if(c=='<') // start of new command + sprintf(commandString,""); + else if(c=='>') { // end of new command +// Serial.print(commandString); + parse(commandString); + } + else if(strlen(commandString)') + } + Sensor::check(); } // while + // Serial.print( "x" ); } - - #endif - } // SerialCommand:process /////////////////////////////////////////////////////////////////////////////// @@ -82,124 +59,10 @@ void SerialCommand::process(){ void SerialCommand::parse(char *com){ switch(com[0]){ - -/***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ - - case 't': // -/* - * sets the throttle for a given register/cab combination - * - * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting - * CAB: the short (1-127) or long (128-10293) address of the engine decoder - * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) - * DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train - * - * returns: - * - */ - mRegs->setThrottle(com+1); - break; - -/***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ - - case 'f': // -/* - * turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL) - * NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program - * - * CAB: the short (1-127) or long (128-10293) address of the engine decoder - * - * To set functions F0-F4 on (=1) or off (=0): - * - * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 - * BYTE2: omitted - * - * To set functions F5-F8 on (=1) or off (=0): - * - * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 - * BYTE2: omitted - * - * To set functions F9-F12 on (=1) or off (=0): - * - * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 - * BYTE2: omitted - * - * To set functions F13-F20 on (=1) or off (=0): - * - * BYTE1: 222 - * BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128 - * - * To set functions F21-F28 on (=1) of off (=0): - * - * BYTE1: 223 - * BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128 - * - * returns: NONE - * - */ - mRegs->setFunction(com+1); - break; - -/***** OPERATE STATIONARY ACCESSORY DECODERS ****/ - - case 'a': // -/* - * turns an accessory (stationary) decoder on or off - * - * ADDRESS: the primary address of the decoder (0-511) - * SUBADDRESS: the subaddress of the decoder (0-3) - * ACTIVATE: 1=on (set), 0=off (clear) - * - * Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N, - * from 1 through a max of 2044, where - * - * N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0 - * - * OR - * - * ADDRESS = INT((N - 1) / 4) + 1 - * SUBADDRESS = (N - 1) % 4 - * - * returns: NONE - */ - mRegs->setAccessory(com+1); - break; - -/***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ - - case 'T': // -/* - * : sets turnout ID to either the "thrown" or "unthrown" position - * - * ID: the numeric ID (0-32767) of the turnout to control - * THROW: 0 (unthrown) or 1 (thrown) - * - * returns: or if turnout ID does not exist - * - * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND - * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS - */ - Turnout::parse(com+1); - break; - -/***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ - - case 'Z': // -/* - * : sets output ID to either the "active" or "inactive" state - * - * ID: the numeric ID (0-32767) of the output to control - * ACTIVATE: 0 (active) or 1 (inactive) - * - * returns: or if output ID does not exist - * - * *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND - * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS - */ - Output::parse(com+1); + case 'T': // process turnouts + case 'Z': // process outputs + client.print(""); // nothing defined for now break; - -/***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ case 'S': /* @@ -218,99 +81,13 @@ void SerialCommand::parse(char *com){ Sensor::status(); break; -/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ - - case 'w': // -/* - * writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track - * - * CAB: the short (1-127) or long (128-10293) address of the engine decoder - * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) - * VALUE: the value to be written to the Configuration Variable memory location (0-255) - * - * returns: NONE -*/ - mRegs->writeCVByteMain(com+1); - break; - -/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ - - case 'b': // -/* - * writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track - * - * CAB: the short (1-127) or long (128-10293) address of the engine decoder - * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) - * BIT: the bit number of the Configurarion Variable regsiter to write (0-7) - * VALUE: the value of the bit to be written (0-1) - * - * returns: NONE -*/ - mRegs->writeCVBitMain(com+1); - break; - -/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ - - case 'W': // -/* - * writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track - * - * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) - * VALUE: the value to be written to the Configuration Variable memory location (0-255) - * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function - * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function - * - * returns: writeCVByte(com+1); - break; - -/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ - - case 'B': // -/* - * writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track - * - * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) - * BIT: the bit number of the Configurarion Variable memory location to write (0-7) - * VALUE: the value of the bit to be written (0-1) - * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function - * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function - * - * returns: writeCVBit(com+1); - break; - -/***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ - - case 'R': // -/* - * reads a Configuration Variable from the decoder of an engine on the programming track - * - * CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024) - * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function - * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function - * - * returns: readCV(com+1); - break; - -/***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ - case '1': // <1> /* * enables power from the motor shield to the main operations and programming tracks * * returns: */ - digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); - digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); - INTERFACE.print(""); + client.print(""); break; /***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ @@ -321,78 +98,34 @@ void SerialCommand::parse(char *com){ * * returns: */ - digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); - digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); - INTERFACE.print(""); + client.print(""); break; -/***** READ MAIN OPERATIONS TRACK CURRENT ****/ - - case 'c': // -/* - * reads current being drawn on main operations track - * - * returns: - * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme - */ - INTERFACE.print("current)); - INTERFACE.print(">"); - break; - /***** READ STATUS OF DCC++ BASE STATION ****/ case 's': // /* * returns status messages containing track power status, throttle status, turn-out status, and a version number - * NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings + * NOTE: this is very useful as a first command for an client to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings * - * returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings + * returns: series of status messages that can be read by an client to determine status of DCC++ Base Station and important settings */ - if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN - INTERFACE.print(""); - else - INTERFACE.print(""); - - for(int i=1;i<=MAX_MAIN_REGISTERS;i++){ - if(mRegs->speedTable[i]==0) - continue; - INTERFACE.print("speedTable[i]>0){ - INTERFACE.print(mRegs->speedTable[i]); - INTERFACE.print(" 1>"); - } else{ - INTERFACE.print(-mRegs->speedTable[i]); - INTERFACE.print(" 0>"); - } - } - INTERFACE.print(""); - - INTERFACE.print(""); - #elif COMM_TYPE == 1 - INTERFACE.print(Ethernet.localIP()); - INTERFACE.print(">"); - #endif - - Turnout::show(); - Output::show(); - + client.print(""); + client.print(""); + client.print(""); + break; /***** STORE SETTINGS IN EEPROM ****/ @@ -405,13 +138,13 @@ void SerialCommand::parse(char *com){ */ EEStore::store(); - INTERFACE.print("data.nTurnouts); - INTERFACE.print(" "); - INTERFACE.print(EEStore::eeStore->data.nSensors); - INTERFACE.print(" "); - INTERFACE.print(EEStore::eeStore->data.nOutputs); - INTERFACE.print(">"); + client.print("data.nTurnouts); + client.print(" "); + client.print(EEStore::eeStore->data.nSensors); + client.print(" "); + client.print(EEStore::eeStore->data.nOutputs); + client.print(">"); break; /***** CLEAR SETTINGS IN EEPROM ****/ @@ -424,7 +157,7 @@ void SerialCommand::parse(char *com){ */ EEStore::clear(); - INTERFACE.print(""); + client.print(""); break; /***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ @@ -435,134 +168,9 @@ void SerialCommand::parse(char *com){ * * returns: a carriage return */ - INTERFACE.println(""); + client.println(""); break; -/// -/// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES -/// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW -/// - -/***** ENTER DIAGNOSTIC MODE ****/ - - case 'D': // -/* - * changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED - * SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS - */ - - Serial.println("\nEntering Diagnostic Mode..."); - delay(1000); - - bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8 - bitSet(TCCR1B,CS11); - bitClear(TCCR1B,CS10); - - #ifdef ARDUINO_AVR_UNO // Configuration for UNO - - bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4 - bitClear(TCCR0B,CS01); - bitClear(TCCR0B,CS00); - - #else // Configuration for MEGA - - bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8 - bitSet(TCCR3B,CS31); - bitClear(TCCR3B,CS30); - - #endif - - CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256 - CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS - - break; - -/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ - - case 'M': // -/* - * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track - * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER - * - * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet - * BYTE1: first hexidecimal byte in the packet - * BYTE2: second hexidecimal byte in the packet - * BYTE3: optional third hexidecimal byte in the packet - * BYTE4: optional fourth hexidecimal byte in the packet - * BYTE5: optional fifth hexidecimal byte in the packet - * - * returns: NONE - */ - mRegs->writeTextPacket(com+1); - break; - -/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ - - case 'P': //

-/* - * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track - * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER - * - * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet - * BYTE1: first hexidecimal byte in the packet - * BYTE2: second hexidecimal byte in the packet - * BYTE3: optional third hexidecimal byte in the packet - * BYTE4: optional fourth hexidecimal byte in the packet - * BYTE5: optional fifth hexidecimal byte in the packet - * - * returns: NONE - */ - pRegs->writeTextPacket(com+1); - break; - -/***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/ - - case 'F': // -/* - * measure amount of free SRAM memory left on the Arduino based on trick found on the internet. - * Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM. - * Unfortunately not very reliable --- would be great to find a better method - * - * returns: - * where MEM is the number of free bytes remaining in the Arduino's SRAM - */ - int v; - INTERFACE.print(""); - break; - -/***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/ - - case 'L': // -/* - * lists the packet contents of the main operations track registers and the programming track registers - * FOR DIAGNOSTIC AND TESTING USE ONLY - */ - INTERFACE.println(""); - for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){ - INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t"); - INTERFACE.print((int)p); INTERFACE.print("\t"); - INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); - INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); - for(int i=0;i<10;i++){ - INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); - } - INTERFACE.println(""); - } - for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){ - INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t"); - INTERFACE.print((int)p); INTERFACE.print("\t"); - INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); - INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); - for(int i=0;i<10;i++){ - INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); - } - INTERFACE.println(""); - } - INTERFACE.println(""); - break; - } // switch }; // SerialCommand::parse diff --git a/DCCpp_Uno/SerialCommand.h b/DCCpp_Uno/SerialCommand.h index 510ce15..6fa92e0 100644 --- a/DCCpp_Uno/SerialCommand.h +++ b/DCCpp_Uno/SerialCommand.h @@ -10,16 +10,10 @@ Part of DCC++ BASE STATION for the Arduino #ifndef SerialCommand_h #define SerialCommand_h -#include "PacketRegister.h" -#include "CurrentMonitor.h" - #define MAX_COMMAND_LENGTH 30 struct SerialCommand{ static char commandString[MAX_COMMAND_LENGTH+1]; - static volatile RegisterList *mRegs, *pRegs; - static CurrentMonitor *mMonitor; - static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *); static void parse(char *); static void process(); }; // SerialCommand From d09e4b9037a567b8d33d8652fda49a5bb76e6cd1 Mon Sep 17 00:00:00 2001 From: dtworth <36244729+dtworth@users.noreply.github.com> Date: Sat, 10 Mar 2018 16:20:42 -0500 Subject: [PATCH 2/7] Renamed from Uno to NodeMCU Also edited the description in contained within the DCCpp_NodeMCU.ino file. --- {DCCpp_Uno => DCCpp_NodeMCU}/Config.h | 0 .../DCCpp_NodeMCU.h | 0 DCCpp_NodeMCU/DCCpp_NodeMCU.ino | 158 +++++++++++ {DCCpp_Uno => DCCpp_NodeMCU}/EEStore.cpp | 2 +- {DCCpp_Uno => DCCpp_NodeMCU}/EEStore.h | 0 {DCCpp_Uno => DCCpp_NodeMCU}/Sensor.cpp | 2 +- {DCCpp_Uno => DCCpp_NodeMCU}/Sensor.h | 0 .../SerialCommand.cpp | 2 +- {DCCpp_Uno => DCCpp_NodeMCU}/SerialCommand.h | 0 DCCpp_Uno/DCCpp_Uno.ino | 252 ------------------ 10 files changed, 161 insertions(+), 255 deletions(-) rename {DCCpp_Uno => DCCpp_NodeMCU}/Config.h (100%) rename DCCpp_Uno/DCCpp_Uno.h => DCCpp_NodeMCU/DCCpp_NodeMCU.h (100%) create mode 100644 DCCpp_NodeMCU/DCCpp_NodeMCU.ino rename {DCCpp_Uno => DCCpp_NodeMCU}/EEStore.cpp (98%) rename {DCCpp_Uno => DCCpp_NodeMCU}/EEStore.h (100%) rename {DCCpp_Uno => DCCpp_NodeMCU}/Sensor.cpp (99%) rename {DCCpp_Uno => DCCpp_NodeMCU}/Sensor.h (100%) rename {DCCpp_Uno => DCCpp_NodeMCU}/SerialCommand.cpp (99%) rename {DCCpp_Uno => DCCpp_NodeMCU}/SerialCommand.h (100%) delete mode 100644 DCCpp_Uno/DCCpp_Uno.ino diff --git a/DCCpp_Uno/Config.h b/DCCpp_NodeMCU/Config.h similarity index 100% rename from DCCpp_Uno/Config.h rename to DCCpp_NodeMCU/Config.h diff --git a/DCCpp_Uno/DCCpp_Uno.h b/DCCpp_NodeMCU/DCCpp_NodeMCU.h similarity index 100% rename from DCCpp_Uno/DCCpp_Uno.h rename to DCCpp_NodeMCU/DCCpp_NodeMCU.h diff --git a/DCCpp_NodeMCU/DCCpp_NodeMCU.ino b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino new file mode 100644 index 0000000..5ca3f9e --- /dev/null +++ b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino @@ -0,0 +1,158 @@ +/********************************************************************** + +DCC++ BASE STATION +COPYRIGHT (c) 2013-2016 Gregg E. Berman + + This program 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. + + This program 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 this program. If not, see http://www.gnu.org/licenses + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega +using the Arduino IDE 1.6.6. + +It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others) +to be used as a fully-functioning digital command and control (DCC) base station +for controlling model train layouts that conform to current National Model +Railroad Association (NMRA) DCC standards. + +This stripped-down version of DCC++ BASE STATION only supports sensor inputs connected +to the NodeMCU. + +DCC++ BASE STATION is controlled with simple text commands received via +the NodeMCU WiFi interface. + +Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or +commercial hardware, software, interfaces, specifications, or methods related +to the control of model trains using NMRA DCC standards. Both programs are wholly +original, developed by the author, and are not derived from any known commercial, +free, or open-source model railroad control packages by any other parties. + +However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and +embedded libraries associated with Arduino and Processing. Tremendous thanks to those +responsible for these terrific open-source initiatives that enable programs like +DCC++ to be developed and distributed in the same fashion. + +REFERENCES: + + NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices + Arduino: http://www.arduino.cc/ + Processing: http://processing.org/ + GNU General Public License: http://opensource.org/licenses/GPL-3.0 + +BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION: + + +DCC++ BASE STATION in split into multiple modules, each with its own header file: + + DCCpp_NodeMCU: declares required global objects and contains initial Arduino setup() + and Arduino loop() functions, as well as and optional array of Sensors + + SerialCommand: contains methods to read and interpret text commands from the WiFi interface, + process those instructions. + + Sensor: contains methods to monitor and report on the status of optionally-defined infrared + sensors embedded in the Main Track and connected to various pins on the NodeMCU + + EEStore: contains methods to store, update, and the sensor settings in the EEPROM for + recall after power-up + +DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters + +**********************************************************************/ + +// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE + +#include +#include "Config.h" +#include "DCCpp_NodeMCU.h" +#include "Sensor.h" +#include "EEstore.h" +#include "SerialCommand.h" + +WiFiServer server(ETHERNET_PORT); +WiFiClient client; + +void connectToWiFi() +{ + Serial.print("\n\nConnecting to "); + Serial.println(_SSID); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + WiFi.begin( _SSID, _PASSWORD ); + WiFi.hostname( _HOSTNAME ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + WiFi.disconnect(); + + Serial.println( WiFi.softAP( _HOSTNAME, _PASSWORD, 6) ? "AP Started": "AP failed" ); + Serial.print("\n\nConnecting to "); + Serial.println( _SSID ); + WiFi.mode(WIFI_AP); + WiFi.begin( _SSID, _PASSWORD ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + server.begin(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// MAIN ARDUINO LOOP +/////////////////////////////////////////////////////////////////////////////// + +void loop(){ + + SerialCommand::process(); // check for, and process, and new serial commands + Sensor::check(); // check sensors for activate/de-activate + +} // loop + +/////////////////////////////////////////////////////////////////////////////// +// INITIAL SETUP +/////////////////////////////////////////////////////////////////////////////// + +void setup(){ + + Serial.begin(115200); // configure serial interface + Serial.flush(); + + EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM + + Serial.print(""); + + Serial.print(""); +} // setup diff --git a/DCCpp_Uno/EEStore.cpp b/DCCpp_NodeMCU/EEStore.cpp similarity index 98% rename from DCCpp_Uno/EEStore.cpp rename to DCCpp_NodeMCU/EEStore.cpp index 01b6488..fc9a6b4 100644 --- a/DCCpp_Uno/EEStore.cpp +++ b/DCCpp_NodeMCU/EEStore.cpp @@ -7,7 +7,7 @@ Part of DCC++ BASE STATION for the Arduino **********************************************************************/ -#include "DCCpp_Uno.h" +#include "DCCpp_NodeMCU.h" #include "EEStore.h" #include "Sensor.h" #include diff --git a/DCCpp_Uno/EEStore.h b/DCCpp_NodeMCU/EEStore.h similarity index 100% rename from DCCpp_Uno/EEStore.h rename to DCCpp_NodeMCU/EEStore.h diff --git a/DCCpp_Uno/Sensor.cpp b/DCCpp_NodeMCU/Sensor.cpp similarity index 99% rename from DCCpp_Uno/Sensor.cpp rename to DCCpp_NodeMCU/Sensor.cpp index af63ef1..20c7f96 100644 --- a/DCCpp_Uno/Sensor.cpp +++ b/DCCpp_NodeMCU/Sensor.cpp @@ -55,7 +55,7 @@ decide to ignore the return and only react to triggers. **********************************************************************/ #include -#include "DCCpp_Uno.h" +#include "DCCpp_NodeMCU.h" #include "Sensor.h" #include "EEStore.h" #include diff --git a/DCCpp_Uno/Sensor.h b/DCCpp_NodeMCU/Sensor.h similarity index 100% rename from DCCpp_Uno/Sensor.h rename to DCCpp_NodeMCU/Sensor.h diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_NodeMCU/SerialCommand.cpp similarity index 99% rename from DCCpp_Uno/SerialCommand.cpp rename to DCCpp_NodeMCU/SerialCommand.cpp index 5fb33bb..37134c9 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_NodeMCU/SerialCommand.cpp @@ -16,7 +16,7 @@ Part of DCC++ BASE STATION for the Arduino #include #include "SerialCommand.h" -#include "DCCpp_Uno.h" +#include "DCCpp_NodeMCU.h" #include "Sensor.h" #include "EEStore.h" diff --git a/DCCpp_Uno/SerialCommand.h b/DCCpp_NodeMCU/SerialCommand.h similarity index 100% rename from DCCpp_Uno/SerialCommand.h rename to DCCpp_NodeMCU/SerialCommand.h diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino deleted file mode 100644 index 50c736e..0000000 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ /dev/null @@ -1,252 +0,0 @@ -/********************************************************************** - -DCC++ BASE STATION -COPYRIGHT (c) 2013-2016 Gregg E. Berman - - This program 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. - - This program 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 this program. If not, see http://www.gnu.org/licenses - -**********************************************************************/ -/********************************************************************** - -DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega -using the Arduino IDE 1.6.6. - -It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others) -to be used as a fully-functioning digital command and control (DCC) base station -for controlling model train layouts that conform to current National Model -Railroad Association (NMRA) DCC standards. - -This version of DCC++ BASE STATION supports: - - * 2-byte and 4-byte locomotive addressing - * Simultaneous control of multiple locomotives - * 128-step speed throttling - * Cab functions F0-F28 - * Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses - - includes optional functionailty to monitor and store of the direction of any connected turnouts - * Programming on the Main Operations Track - - write configuration variable bytes - - set/clear specific configuration variable bits - * Programming on the Programming Track - - write configuration variable bytes - - set/clear specific configuration variable bits - - read configuration variable bytes - -DCC++ BASE STATION is controlled with simple text commands received via -the Arduino's serial interface. Users can type these commands directly -into the Arduino IDE Serial Monitor, or can send such commands from another -device or computer program. - -When compiled for the Arduino Mega, an Ethernet Shield can be used for network -communications instead of using serial communications. - -DCC++ CONTROLLER, available separately under a similar open-source -license, is a Java program written using the Processing library and Processing IDE -that provides a complete and configurable graphic interface to control model train layouts -via the DCC++ BASE STATION. - -With the exception of a standard 15V power supply that can be purchased in -any electronics store, no additional hardware is required. - -Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or -commercial hardware, software, interfaces, specifications, or methods related -to the control of model trains using NMRA DCC standards. Both programs are wholly -original, developed by the author, and are not derived from any known commercial, -free, or open-source model railroad control packages by any other parties. - -However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and -embedded libraries associated with Arduino and Processing. Tremendous thanks to those -responsible for these terrific open-source initiatives that enable programs like -DCC++ to be developed and distributed in the same fashion. - -REFERENCES: - - NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices - Arduino: http://www.arduino.cc/ - Processing: http://processing.org/ - GNU General Public License: http://opensource.org/licenses/GPL-3.0 - -BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION: - -DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0, -and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V -unipolar signals that each properly encode zero and one bits conforming with -DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B. - -Series of DCC bit streams are bundled into Packets that each form the basis of -a standard DCC instruction. Packets are stored in Packet Registers that contain -methods for updating and queuing according to text commands sent by the user -(or another program) over the serial interface. There is one set of registers that controls -the main operations track and one that controls the programming track. - -For the main operations track, packets to store cab throttle settings are stored in -registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp_Uno.h). -It is generally considered good practice to continuously send throttle control packets -to every cab so that if an engine should momentarily lose electrical connectivity with the tracks, -it will very quickly receive another throttle control signal as soon as connectivity is -restored (such as when a trin passes over rough portion of track or the frog of a turnout). - -DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter -that has been loaded with a throttle control setting for a given cab. For each register, it -transmits the appropriate DCC packet bits to the track, then moves onto the next register -without any pausing to ensure continuous bi-polar power is being provided to the tracks. -Updates to the throttle setting stored in any given packet register are done in a double-buffered -fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits -can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal. -The cabs identified in each stored throttle setting should be unique across registers. If two registers -contain throttle setting for the same cab, the throttle in the engine will oscillate between the two, -which is probably not a desireable outcome. - -For both the main operations track and the programming track there is also a special packet register with id=0 -that is used to store all other DCC packets that do not require continious transmittal to the tracks. -This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables. -It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure -proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet -and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself. -Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of -verifying proper transmittal before acting on the instructions contained in those packets - -An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached -on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals -produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals. - -This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10) -to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect -the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13). - -For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the -Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2) -to the Motor Shield's DIRECTION B input (pin 13). - -Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp_Uno.h for details). - -When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be -connected directly to the tracks. This software assumes CHANNEL A is connected -to the Main Operations Track, and CHANNEL B is connected to the Programming Track. - -DCC++ BASE STATION in split into multiple modules, each with its own header file: - - DCCpp_Uno: declares required global objects and contains initial Arduino setup() - and Arduino loop() functions, as well as interrput code for OC0B and OC1B. - Also includes declarations of optional array of Turn-Outs and optional array of Sensors - - SerialCommand: contains methods to read and interpret text commands from the serial line, - process those instructions, and, if necessary call appropriate Packet RegisterList methods - to update either the Main Track or Programming Track Packet Registers - - PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions - - CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and - CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload - is detected - - Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled - by a DCC stationary accessory decoder. - - Sensor: contains methods to monitor and report on the status of optionally-defined infrared - sensors embedded in the Main Track and connected to various pins on the Arudino Uno - - Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use - - EEStore: contains methods to store, update, and load various DCC settings and status - (e.g. the states of all defined turnouts) in the EEPROM for recall after power-up - -DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters - -**********************************************************************/ - -// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE - -#include -#include "Config.h" -#include "DCCpp_Uno.h" -#include "Sensor.h" -#include "EEstore.h" -#include "SerialCommand.h" - -WiFiServer server(ETHERNET_PORT); -WiFiClient client; - -void connectToWiFi() -{ - Serial.print("\n\nConnecting to "); - Serial.println(_SSID); - WiFi.mode(WIFI_STA); - WiFi.disconnect(); - WiFi.begin( _SSID, _PASSWORD ); - WiFi.hostname( _HOSTNAME ); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println("\nWiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - WiFi.disconnect(); - - Serial.println( WiFi.softAP( _HOSTNAME, _PASSWORD, 6) ? "AP Started": "AP failed" ); - Serial.print("\n\nConnecting to "); - Serial.println( _SSID ); - WiFi.mode(WIFI_AP); - WiFi.begin( _SSID, _PASSWORD ); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println("\nWiFi connected"); - server.begin(); -} - - -/////////////////////////////////////////////////////////////////////////////// -// MAIN ARDUINO LOOP -/////////////////////////////////////////////////////////////////////////////// - -void loop(){ - - SerialCommand::process(); // check for, and process, and new serial commands - Sensor::check(); // check sensors for activate/de-activate - -} // loop - -/////////////////////////////////////////////////////////////////////////////// -// INITIAL SETUP -/////////////////////////////////////////////////////////////////////////////// - -void setup(){ - - Serial.begin(115200); // configure serial interface - Serial.flush(); - - EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM - - Serial.print(""); - - Serial.print(""); -} // setup From 99e923ad9ca21e33828b1231aa98f0cb9232d8a7 Mon Sep 17 00:00:00 2001 From: Dan Worth <36244729+dtworth@users.noreply.github.com> Date: Sat, 10 Mar 2018 16:28:05 -0500 Subject: [PATCH 3/7] Update README.md --- README.md | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9b63ac8..c69071b 100644 --- a/README.md +++ b/README.md @@ -12,26 +12,12 @@ The DCC++ Controller provides operators with a customizable GUI to control their What’s in this Repository ------------------------- -This repository, BaseStation-Uno, contains a complete DCC++ Base Station sketch designed for compiling and uploading into an Arduino Uno. All sketch files are in the folder named DCCpp_Uno. More information about the sketch can be found in the included PDF file. +This repository, BaseStation-NodeMCU, contains a stripped-down DCC++ Base Station sketch designed for compiling and uploading into a NodeMCU. All sketch files are in the folder named DCCpp_NodeMCU. More information about the sketch can be found in the included PDF file. -To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_Uno.ino within the DCCpp_Uno folder using your Arduino IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. +To utilize this sketch, simply download a zip file of this repository and open the file DCCpp_NodeMCU.ino within the DCCpp_NodeMCU folder using your Arduino IDE. Please do not rename the folder containing the sketch code, nor add any files to that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. -The latest production release of the Master branch is 1.2.1: - -* Supports both the Arduino Uno and Arduino Mega -* Built-in configuration for both the original Arduino Motor Shield as well as a Pololu MC33926 Motor Shield -* Built-in configuration and support of Ethernet Shields (for use with Mega only) +This particular version only supports sensor inputs. For more information on the overall DCC++ system, please follow the links in the PDF file. -Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository - -The Master branch contains all of the Base Station functionality showed in the DCC++ YouTube channel with the exception of 2 layout-specific modules: - -* Control for an RGB LED Light Strip using pins 44, 45, and 46 on the Mega -* An embedded AutoPilot routine that randomly selects a train to run through the entire layout, after which it is brought back into its original siding and the the patterns repeats with another randomly-selected train. This is the AutoPilot routine showed on the DCC++ YouTube channel. It does not require any computer, not DCC++ Controller to be running (DCC++ Controller contains a much more complicated 3-train Auto Pilot mode, also as shown on the DCC++ YouTube channel). - -Since these modules are very layout-specififc, they are not included in the Master branch. However, they are included in the Development branch. Please feel free to download and copy any relevant code to customize your own version of DCC++ Base Station. - --December 27, 2015 - +-March 10, 2017 From 2952f81283ee6f4fa115788951c73cb5eb5204f6 Mon Sep 17 00:00:00 2001 From: dtworth <36244729+dtworth@users.noreply.github.com> Date: Thu, 5 Apr 2018 18:00:33 -0400 Subject: [PATCH 4/7] Implimented multiple clients on WiFi --- DCCpp_NodeMCU/Config.h | 3 +- DCCpp_NodeMCU/DCCpp_NodeMCU.ino | 50 +------ DCCpp_NodeMCU/EEStore.cpp | 2 - DCCpp_NodeMCU/Sensor.cpp | 51 ++++--- DCCpp_NodeMCU/SerialCommand.cpp | 179 ------------------------ DCCpp_NodeMCU/SerialCommand.h | 25 ---- DCCpp_NodeMCU/WiFiCommand.cpp | 241 ++++++++++++++++++++++++++++++++ DCCpp_NodeMCU/WiFiCommand.h | 26 ++++ 8 files changed, 301 insertions(+), 276 deletions(-) delete mode 100644 DCCpp_NodeMCU/SerialCommand.cpp delete mode 100644 DCCpp_NodeMCU/SerialCommand.h create mode 100644 DCCpp_NodeMCU/WiFiCommand.cpp create mode 100644 DCCpp_NodeMCU/WiFiCommand.h diff --git a/DCCpp_NodeMCU/Config.h b/DCCpp_NodeMCU/Config.h index 98b40a2..9282618 100644 --- a/DCCpp_NodeMCU/Config.h +++ b/DCCpp_NodeMCU/Config.h @@ -11,6 +11,7 @@ Part of DCC++ BASE STATION for the Arduino // // DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE // +///////////////////////////////////////////////////////////////////////////////////// #define ETHERNET_PORT 2560 @@ -22,4 +23,4 @@ Part of DCC++ BASE STATION for the Arduino #define _SSID "" #define _PASSWORD "" -#define _HOSTNAME "block01" +#define _HOSTNAME "sensor02" diff --git a/DCCpp_NodeMCU/DCCpp_NodeMCU.ino b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino index 5ca3f9e..39db3ed 100644 --- a/DCCpp_NodeMCU/DCCpp_NodeMCU.ino +++ b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino @@ -59,7 +59,7 @@ DCC++ BASE STATION in split into multiple modules, each with its own header file DCCpp_NodeMCU: declares required global objects and contains initial Arduino setup() and Arduino loop() functions, as well as and optional array of Sensors - SerialCommand: contains methods to read and interpret text commands from the WiFi interface, + WiFiCommand: contains methods to read and interpret text commands from the WiFi interface, process those instructions. Sensor: contains methods to monitor and report on the status of optionally-defined infrared @@ -79,41 +79,7 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "DCCpp_NodeMCU.h" #include "Sensor.h" #include "EEstore.h" -#include "SerialCommand.h" - -WiFiServer server(ETHERNET_PORT); -WiFiClient client; - -void connectToWiFi() -{ - Serial.print("\n\nConnecting to "); - Serial.println(_SSID); - WiFi.mode(WIFI_STA); - WiFi.disconnect(); - WiFi.begin( _SSID, _PASSWORD ); - WiFi.hostname( _HOSTNAME ); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println("\nWiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - WiFi.disconnect(); - - Serial.println( WiFi.softAP( _HOSTNAME, _PASSWORD, 6) ? "AP Started": "AP failed" ); - Serial.print("\n\nConnecting to "); - Serial.println( _SSID ); - WiFi.mode(WIFI_AP); - WiFi.begin( _SSID, _PASSWORD ); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println("\nWiFi connected"); - server.begin(); -} - +#include "WiFiCommand.h" /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP @@ -121,8 +87,8 @@ void connectToWiFi() void loop(){ - SerialCommand::process(); // check for, and process, and new serial commands - Sensor::check(); // check sensors for activate/de-activate + WiFiCommand::process(); // check for, and process, and new WiFi commands + Sensor::check(); // check sensors for activate/de-activate } // loop @@ -136,7 +102,8 @@ void setup(){ Serial.flush(); EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM - + WiFiCommand::init(); + Serial.print(""); - Serial.print(""); + Serial.println(">"); } // setup diff --git a/DCCpp_NodeMCU/EEStore.cpp b/DCCpp_NodeMCU/EEStore.cpp index fc9a6b4..25bceba 100644 --- a/DCCpp_NodeMCU/EEStore.cpp +++ b/DCCpp_NodeMCU/EEStore.cpp @@ -15,8 +15,6 @@ Part of DCC++ BASE STATION for the Arduino /////////////////////////////////////////////////////////////////////////////// void EEStore::init(){ - - eeStore=(EEStore *)calloc(1,sizeof(EEStore)); EEPROM.begin( 4096 ); diff --git a/DCCpp_NodeMCU/Sensor.cpp b/DCCpp_NodeMCU/Sensor.cpp index 20c7f96..93463f6 100644 --- a/DCCpp_NodeMCU/Sensor.cpp +++ b/DCCpp_NodeMCU/Sensor.cpp @@ -55,14 +55,13 @@ decide to ignore the return and only react to triggers. **********************************************************************/ #include +#include "Config.h" #include "DCCpp_NodeMCU.h" #include "Sensor.h" #include "EEStore.h" +#include "WiFiCommand.h" #include -extern WiFiClient client; -extern WiFiServer server; - /////////////////////////////////////////////////////////////////////////////// void Sensor::check(){ @@ -73,14 +72,14 @@ void Sensor::check(){ if(!tt->active && tt->signal<0.5){ tt->active=true; - client.print("data.snum); - client.print(">"); + WiFiCommand::print("data.snum); + WiFiCommand::print(">"); } else if(tt->active && tt->signal>0.9){ tt->active=false; - client.print("data.snum); - client.print(">"); + WiFiCommand::print("data.snum); + WiFiCommand::print(">"); } } // loop over all sensors @@ -104,7 +103,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ if(tt==NULL){ // problem allocating memory if(v==1) - client.print(""); + WiFiCommand::print(""); return(tt); } @@ -117,7 +116,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor if(v==1) - client.print(""); + WiFiCommand::print(""); return(tt); } @@ -137,7 +136,7 @@ void Sensor::remove(int n){ for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor); if(tt==NULL){ - client.print(""); + WiFiCommand::print(""); return; } @@ -148,7 +147,7 @@ void Sensor::remove(int n){ free(tt); - client.print(""); + WiFiCommand::print(""); } /////////////////////////////////////////////////////////////////////////////// @@ -157,18 +156,18 @@ void Sensor::show(){ Sensor *tt; if(firstSensor==NULL){ - client.print(""); + WiFiCommand::print(""); return; } for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - client.print("data.snum); - client.print(" "); - client.print(tt->data.pin); - client.print(" "); - client.print(tt->data.pullUp); - client.print(">"); + WiFiCommand::print("data.snum); + WiFiCommand::print(" "); + WiFiCommand::print(tt->data.pin); + WiFiCommand::print(" "); + WiFiCommand::print(tt->data.pullUp); + WiFiCommand::print(">"); } } @@ -178,14 +177,14 @@ void Sensor::status(){ Sensor *tt; if(firstSensor==NULL){ - client.print(""); + WiFiCommand::print(""); return; } for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - client.print(tt->active?"data.snum); - client.print(">"); + WiFiCommand::print(tt->active?"data.snum); + WiFiCommand::print(">"); } } @@ -210,7 +209,7 @@ void Sensor::parse(char *c){ break; case 2: // invalid number of arguments - client.print(""); + WiFiCommand::print(""); break; } } diff --git a/DCCpp_NodeMCU/SerialCommand.cpp b/DCCpp_NodeMCU/SerialCommand.cpp deleted file mode 100644 index 37134c9..0000000 --- a/DCCpp_NodeMCU/SerialCommand.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/********************************************************************** - -SerialCommand.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -// DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS -// WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS -// ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER -// COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. - -// See SerialCommand::parse() below for defined text commands. - -#include -#include "SerialCommand.h" -#include "DCCpp_NodeMCU.h" -#include "Sensor.h" -#include "EEStore.h" - -extern WiFiClient client; -extern WiFiServer server; - -/////////////////////////////////////////////////////////////////////////////// - -char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; - -/////////////////////////////////////////////////////////////////////////////// - -void SerialCommand::process(){ - char c; - - client = server.available(); - - if(client){ -// Serial.print("^" ); - while( client.connected() ) { - if( client.available()){ // while there is data on the network - c=client.read(); - if(c=='<') // start of new command - sprintf(commandString,""); - else if(c=='>') { // end of new command -// Serial.print(commandString); - parse(commandString); - } - else if(strlen(commandString)') - } - Sensor::check(); - } // while - // Serial.print( "x" ); - } -} // SerialCommand:process - -/////////////////////////////////////////////////////////////////////////////// - -void SerialCommand::parse(char *com){ - - switch(com[0]){ - case 'T': // process turnouts - case 'Z': // process outputs - client.print(""); // nothing defined for now - break; - - case 'S': -/* - * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND - * USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS - */ - Sensor::parse(com+1); - break; - -/***** SHOW STATUS OF ALL SENSORS ****/ - - case 'Q': // -/* - * returns: the status of each sensor ID in the form (active) or (not active) - */ - Sensor::status(); - break; - - case '1': // <1> -/* - * enables power from the motor shield to the main operations and programming tracks - * - * returns: - */ - client.print(""); - break; - -/***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ - - case '0': // <0> -/* - * disables power from the motor shield to the main operations and programming tracks - * - * returns: - */ - client.print(""); - break; - -/***** READ STATUS OF DCC++ BASE STATION ****/ - - case 's': // -/* - * returns status messages containing track power status, throttle status, turn-out status, and a version number - * NOTE: this is very useful as a first command for an client to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings - * - * returns: series of status messages that can be read by an client to determine status of DCC++ Base Station and important settings - */ - client.print(""); - client.print(""); - client.print(""); - - break; - -/***** STORE SETTINGS IN EEPROM ****/ - - case 'E': // -/* - * stores settings for turnouts and sensors EEPROM - * - * returns: -*/ - - EEStore::store(); - client.print("data.nTurnouts); - client.print(" "); - client.print(EEStore::eeStore->data.nSensors); - client.print(" "); - client.print(EEStore::eeStore->data.nOutputs); - client.print(">"); - break; - -/***** CLEAR SETTINGS IN EEPROM ****/ - - case 'e': // -/* - * clears settings for Turnouts in EEPROM - * - * returns: -*/ - - EEStore::clear(); - client.print(""); - break; - -/***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ - - case ' ': // < > -/* - * simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window - * - * returns: a carriage return -*/ - client.println(""); - break; - - } // switch -}; // SerialCommand::parse - -/////////////////////////////////////////////////////////////////////////////// - - diff --git a/DCCpp_NodeMCU/SerialCommand.h b/DCCpp_NodeMCU/SerialCommand.h deleted file mode 100644 index 6fa92e0..0000000 --- a/DCCpp_NodeMCU/SerialCommand.h +++ /dev/null @@ -1,25 +0,0 @@ -/********************************************************************** - -SerialCommand.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#ifndef SerialCommand_h -#define SerialCommand_h - -#define MAX_COMMAND_LENGTH 30 - -struct SerialCommand{ - static char commandString[MAX_COMMAND_LENGTH+1]; - static void parse(char *); - static void process(); -}; // SerialCommand - -#endif - - - - diff --git a/DCCpp_NodeMCU/WiFiCommand.cpp b/DCCpp_NodeMCU/WiFiCommand.cpp new file mode 100644 index 0000000..fd23451 --- /dev/null +++ b/DCCpp_NodeMCU/WiFiCommand.cpp @@ -0,0 +1,241 @@ +/********************************************************************** + +WiFiCommand.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +// DCC++ BASE STATION COMMUNICATES VIA WIFI USING SINGLE-CHARACTER TEXT COMMANDS +// WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS +// ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER +// COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. + +// See WiFiCommand::parse() below for defined text commands. + +#include +#include "Config.h" +#include "DCCpp_NodeMCU.h" +#include "WiFiCommand.h" +#include "Sensor.h" +#include "EEStore.h" + +WiFiServer server(ETHERNET_PORT); +WiFiClient client[MAX_CLIENTS]; + +/////////////////////////////////////////////////////////////////////////////// + +char WiFiCommand::commandString[MAX_CLIENTS][MAX_COMMAND_LENGTH+1]; + +/////////////////////////////////////////////////////////////////////////////// + +void WiFiCommand::init(){ + Serial.print("\n\nConnecting to "); + Serial.println(_SSID); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + WiFi.begin( _SSID, _PASSWORD ); + WiFi.hostname( _HOSTNAME ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + WiFi.disconnect(); + + Serial.println( WiFi.softAP( _HOSTNAME, _PASSWORD, 6) ? "AP Started": "AP failed" ); + Serial.print("\n\nConnecting to "); + Serial.println( _SSID ); + WiFi.mode(WIFI_AP); + WiFi.begin( _SSID, _PASSWORD ); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWiFi connected"); + + server.begin(); +} + + +void WiFiCommand::process(){ + char c; + + if (server.hasClient()) { + for (int i = 0; i < MAX_CLIENTS; i++) { + if (!client[i] || !client[i].connected()) { + if (client[i]) { + client[i].stop(); + } + client[i] = server.available(); + continue; + } + } + server.available().stop(); + } + for (int i = 0; i < MAX_CLIENTS; i++) + if (client[i] && client[i].connected()) { + if( client[i].connected() ) { + if( client[i].available()){ // while there is data on the network + c=client[i].read(); + if(c=='<') // start of new command + sprintf(commandString[i],""); + else if(c=='>') { // end of new command +// Serial.print(commandString); + parse(commandString[i]); + } + else if(strlen(commandString[i])') + } +// Sensor::check(); + } // while + } +} // WiFiCommand:process + +/////////////////////////////////////////////////////////////////////////////// + +void WiFiCommand::parse(char *com){ + + switch(com[0]){ + case 'T': // process turnouts + case 'Z': // process outputs + WiFiCommand::print(""); // nothing defined for now + break; + + case 'S': +/* + * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND + * USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS + */ + Sensor::parse(com+1); + break; + +/***** SHOW STATUS OF ALL SENSORS ****/ + + case 'Q': // +/* + * returns: the status of each sensor ID in the form (active) or (not active) + */ + Sensor::status(); + break; + + case '1': // <1> +/* + * enables power from the motor shield to the main operations and programming tracks + * + * returns: + */ + WiFiCommand::print(""); + break; + +/***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ + + case '0': // <0> +/* + * disables power from the motor shield to the main operations and programming tracks + * + * returns: + */ + WiFiCommand::print(""); + break; + +/***** READ STATUS OF DCC++ BASE STATION ****/ + + case 's': // +/* + * returns status messages containing track power status, throttle status, turn-out status, and a version number + * NOTE: this is very useful as a first command for an client to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings + * + * returns: series of status messages that can be read by an client to determine status of DCC++ Base Station and important settings + */ + WiFiCommand::print(""); + WiFiCommand::print(""); + WiFiCommand::print(""); + + break; + +/***** STORE SETTINGS IN EEPROM ****/ + + case 'E': // +/* + * stores settings for turnouts and sensors EEPROM + * + * returns: +*/ + + EEStore::store(); + WiFiCommand::print("data.nTurnouts); + WiFiCommand::print(" "); + WiFiCommand::print(EEStore::eeStore->data.nSensors); + WiFiCommand::print(" "); + WiFiCommand::print(EEStore::eeStore->data.nOutputs); + WiFiCommand::print(">"); + break; + +/***** CLEAR SETTINGS IN EEPROM ****/ + + case 'e': // +/* + * clears settings for Turnouts in EEPROM + * + * returns: +*/ + + EEStore::clear(); + WiFiCommand::print(""); + break; + +/***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ + + case ' ': // < > +/* + * simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window + * + * returns: a carriage return +*/ + WiFiCommand::print("\r"); + break; + + } // switch +}; // WiFiCommand::parse + +/////////////////////////////////////////////////////////////////////////////// + +void WiFiCommand::print( char * string ) { + for (int i = 0; i < MAX_CLIENTS; i++) + if (client[i] && client[i].connected()) { + client[i].print(string); + delay(1); + } +} + + +void WiFiCommand::print(const char * string ) { + char buffer[30]; + strcpy( buffer, string ); + WiFiCommand::print( buffer ); +} + + +void WiFiCommand::print(int num) { + char buffer[30]; + itoa( num, buffer, 10 ); + WiFiCommand::print( buffer ); +} + diff --git a/DCCpp_NodeMCU/WiFiCommand.h b/DCCpp_NodeMCU/WiFiCommand.h new file mode 100644 index 0000000..554d5d0 --- /dev/null +++ b/DCCpp_NodeMCU/WiFiCommand.h @@ -0,0 +1,26 @@ +/********************************************************************** + +WiFiCommand.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef WiFiCommand_h +#define WiFiCommand_h + +#define MAX_COMMAND_LENGTH 30 +#define MAX_CLIENTS 8 + +struct WiFiCommand{ + static char commandString[MAX_CLIENTS][MAX_COMMAND_LENGTH+1]; + static void init(); + static void parse(char *); + static void process(); + static void print(char *); + static void print(const char * ); + static void print(int); +}; // WiFiCommand + +#endif From 47db0d2a40d68466ea327d1dc357078617790e0a Mon Sep 17 00:00:00 2001 From: Dan Worth <36244729+dtworth@users.noreply.github.com> Date: Thu, 5 Apr 2018 18:10:26 -0400 Subject: [PATCH 5/7] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c69071b..54cea75 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,8 @@ This particular version only supports sensor inputs. For more information on the overall DCC++ system, please follow the links in the PDF file. --March 10, 2017 +-March 10, 2018 + +Support for multiple WiFi clients has been added + +-April 5, 2018 From 37cc9bff03b9dfc9c18ae0e93612c69f3fb3422d Mon Sep 17 00:00:00 2001 From: dtworth <36244729+dtworth@users.noreply.github.com> Date: Sat, 7 Apr 2018 16:11:24 -0400 Subject: [PATCH 6/7] Remote Sensors now supported Similar to the way it is implemented in DCC++32 --- DCCpp_NodeMCU/Config.h | 8 ++ DCCpp_NodeMCU/DCCpp_NodeMCU.ino | 3 +- DCCpp_NodeMCU/RemoteSensor.cpp | 190 ++++++++++++++++++++++++++++++++ DCCpp_NodeMCU/RemoteSensor.h | 38 +++++++ DCCpp_NodeMCU/Sensor.cpp | 40 ++++--- DCCpp_NodeMCU/Sensor.h | 2 +- DCCpp_NodeMCU/WiFiCommand.cpp | 12 +- 7 files changed, 274 insertions(+), 19 deletions(-) create mode 100644 DCCpp_NodeMCU/RemoteSensor.cpp create mode 100644 DCCpp_NodeMCU/RemoteSensor.h diff --git a/DCCpp_NodeMCU/Config.h b/DCCpp_NodeMCU/Config.h index 9282618..e439980 100644 --- a/DCCpp_NodeMCU/Config.h +++ b/DCCpp_NodeMCU/Config.h @@ -24,3 +24,11 @@ Part of DCC++ BASE STATION for the Arduino #define _SSID "" #define _PASSWORD "" #define _HOSTNAME "sensor02" + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE REMOTE SENSOR PARAMETERS +// +///////////////////////////////////////////////////////////////////////////////////// + +#define REMOTE_SENSORS_FIRST_SENSOR 100 diff --git a/DCCpp_NodeMCU/DCCpp_NodeMCU.ino b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino index 39db3ed..73c57d0 100644 --- a/DCCpp_NodeMCU/DCCpp_NodeMCU.ino +++ b/DCCpp_NodeMCU/DCCpp_NodeMCU.ino @@ -78,6 +78,7 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "Config.h" #include "DCCpp_NodeMCU.h" #include "Sensor.h" +#include "RemoteSensor.h" #include "EEstore.h" #include "WiFiCommand.h" @@ -88,8 +89,8 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use void loop(){ WiFiCommand::process(); // check for, and process, and new WiFi commands + RemoteSensor::check(); // check remotesensors for inactivity Sensor::check(); // check sensors for activate/de-activate - } // loop /////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_NodeMCU/RemoteSensor.cpp b/DCCpp_NodeMCU/RemoteSensor.cpp new file mode 100644 index 0000000..b8feef8 --- /dev/null +++ b/DCCpp_NodeMCU/RemoteSensor.cpp @@ -0,0 +1,190 @@ +/********************************************************************** + +RemoteSensor.cpp +COPYRIGHT (c) 2018 Dan Worth + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION supports remote RemoteSensor inputs that are connected via a +WiFi connection. Remote RemoteSensors are dynamically created during startup or by a +remote RemoteSensor reporting its state. + +During startup, the base station scans for Access Points that have a name +starting with REMOTE_RemoteSensorS_PREFIX defined in Config.h, ie: "RemoteSensor01". If no +Access Points are found matching this prefix during startup they will be created +automatically when the RemoteSensor reports its state to the base station. + +Note: Remote RemoteSensors should not maintain a persistent connection. Instead they +should connect when a change occurs that should be reported. It is not necessary +for Remote RemoteSensors to report when they are INACTIVE. If a Remote RemoteSensor does not +report within REMOTE_RemoteSensorS_DECAY milliseconds the base station will +automatically transition the Remote RemoteSensor to INACTIVE state if it was +previously ACTIVE. + +To have this sketch monitor one or more for remote RemoteSensor triggers, first define/edit/delete +RemoteSensor definitions using the following variation of the "RS" command: + + : Informs the base station of the status of a remote RemoteSensor. + : Deletes remote RemoteSensor ID. + : Lists all defined remote RemoteSensors. + returns: for each defined remote RemoteSensor or + if no remote RemoteSensors have been defined/found. +where + + ID: the numeric ID (0-32667) of the remote RemoteSensor. + STATE: State of the RemoteSensors, zero is INACTIVE, non-zero is ACTIVE. + Usage is remote RemoteSensor dependent. + +If a Remote RemoteSensor Pin is found to have transitioned from one state to another, one of the following messages are generated: + + - for transition of RemoteSensor ID from HIGH state to LOW state (i.e. the RemoteSensor is triggered) + - for transition of RemoteSensor ID from LOW state to HIGH state (i.e. the RemoteSensor is no longer triggered) + +Depending on whether the physical RemoteSensor is acting as an "event-trigger" or a "detection-RemoteSensor," you may +decide to ignore the return and only react to triggers. + +**********************************************************************/ +#include +#include "Config.h" +#include "DCCpp_NodeMCU.h" +#include "RemoteSensor.h" +#include "EEStore.h" +#include "WiFiCommand.h" +#include + +/////////////////////////////////////////////////////////////////////////////// + +void RemoteSensor::check(){ + RemoteSensor *tt; + Sensor *stt; + + for(tt=firstRemoteSensor;tt!=NULL;tt=tt->nextRemoteSensor) + if( millis() > tt->lastUpdate + REMOTE_SENSORS_DECAY ) + if( stt = Sensor::get( tt->data.snum + REMOTE_SENSORS_FIRST_SENSOR ) ) + stt->signal = 1.0; // set to inactive on the next sensor check + else + Serial.println( "sensor not found after timeout" ); + +} // RemoteSensor::check + +/////////////////////////////////////////////////////////////////////////////// + +RemoteSensor *RemoteSensor::create(int snum, int value ){ + RemoteSensor *tt; + Sensor *stt; + + if(firstRemoteSensor==NULL){ + firstRemoteSensor=(RemoteSensor *)calloc(1,sizeof(RemoteSensor)); + tt=firstRemoteSensor; + } else if((tt=get(snum))==NULL){ + tt=firstRemoteSensor; + while(tt->nextRemoteSensor!=NULL) + tt=tt->nextRemoteSensor; + tt->nextRemoteSensor=(RemoteSensor *)calloc(1,sizeof(RemoteSensor)); + tt=tt->nextRemoteSensor; + } + + if(tt==NULL){ // problem allocating memory + WiFiCommand::print(""); + return(tt); + } + + tt->lastUpdate = millis(); + tt->data.snum=snum; + tt->data.value=value; + if( !(stt = Sensor::get( snum + REMOTE_SENSORS_FIRST_SENSOR ) ) ) + stt = Sensor::create(snum + REMOTE_SENSORS_FIRST_SENSOR, -1, 0, 0 ); + + if( stt ) { + stt->active=value?false:true; // force a state change on the next sensor check + stt->signal=value?0.0:1.0; + } + else + Serial.println( "no sensor created or found" ); + WiFiCommand::print(""); + return(tt); +} + +/////////////////////////////////////////////////////////////////////////////// + +RemoteSensor* RemoteSensor::get(int n){ + RemoteSensor *tt; + for(tt=firstRemoteSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextRemoteSensor); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +void RemoteSensor::remove(int n){ + RemoteSensor *tt,*pp; + + for(tt=firstRemoteSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextRemoteSensor); + + if(tt==NULL){ + WiFiCommand::print(""); + return; + } + Sensor::remove( tt->data.snum + REMOTE_SENSORS_FIRST_SENSOR ); + + if(tt==firstRemoteSensor) + firstRemoteSensor=tt->nextRemoteSensor; + else + pp->nextRemoteSensor=tt->nextRemoteSensor; + + + free(tt); + + WiFiCommand::print(""); +} + +/////////////////////////////////////////////////////////////////////////////// + +void RemoteSensor::show(){ + RemoteSensor *tt; + + if(firstRemoteSensor==NULL){ + WiFiCommand::print(""); + return; + } + + for(tt=firstRemoteSensor;tt!=NULL;tt=tt->nextRemoteSensor){ + WiFiCommand::print("data.snum); + WiFiCommand::print(" "); + WiFiCommand::print(tt->data.value); + WiFiCommand::print(">"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void RemoteSensor::parse(char *c){ + int n,s,m; + RemoteSensor *t; + + switch(sscanf(c,"%d %d",&n,&s)){ + + case 2: // argument is string with id number of RemoteSensor followed by a value + create(n,s); + break; + + case 1: // argument is a string with id number only + remove(n); + break; + + case -1: // no arguments + show(); + break; + + default: // invalid number of arguments + WiFiCommand::print(""); + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +RemoteSensor *RemoteSensor::firstRemoteSensor=NULL; + diff --git a/DCCpp_NodeMCU/RemoteSensor.h b/DCCpp_NodeMCU/RemoteSensor.h new file mode 100644 index 0000000..aacbfe6 --- /dev/null +++ b/DCCpp_NodeMCU/RemoteSensor.h @@ -0,0 +1,38 @@ +/********************************************************************** + +Sensor.h +COPYRIGHT (c) 2018 Dan Worth + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef RemoteSensor_h +#define RemoteSensor_h + +#include "Sensor.h" + +#define REMOTE_SENSORS_PREFIX "sensor" +#define REMOTE_SENSORS_DECAY 10000 + +struct RemoteSensorData { + int snum; + int value; +}; + +struct RemoteSensor{ + static RemoteSensor *firstRemoteSensor; + RemoteSensorData data; + long lastUpdate; + RemoteSensor *nextRemoteSensor; + static RemoteSensor *create(int, int); + static RemoteSensor* get(int); + static void remove(int); +// static void status(); + static void show(); + static void parse(char *c); + static void check(); +}; // Remote Sensor + +#endif + diff --git a/DCCpp_NodeMCU/Sensor.cpp b/DCCpp_NodeMCU/Sensor.cpp index 93463f6..e644dfd 100644 --- a/DCCpp_NodeMCU/Sensor.cpp +++ b/DCCpp_NodeMCU/Sensor.cpp @@ -68,14 +68,15 @@ void Sensor::check(){ Sensor *tt; for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ - tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; + if( tt->data.pin >= 0 ) // not for remote sensors + tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; - if(!tt->active && tt->signal<0.5){ + if(!tt->active && (tt->signal<0.5) ){ tt->active=true; WiFiCommand::print("data.snum); WiFiCommand::print(">"); - } else if(tt->active && tt->signal>0.9){ + } else if(tt->active && (tt->signal>0.9) ){ tt->active=false; WiFiCommand::print("data.snum); @@ -110,11 +111,12 @@ Sensor *Sensor::create(int snum, int pin, int pullUp, int v){ tt->data.snum=snum; tt->data.pin=pin; tt->data.pullUp=(pullUp==0?LOW:HIGH); - tt->active=false; - tt->signal=1; - pinMode(pin,INPUT); // set mode to input - digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor - + if( pin >= 0 ) { // don't do this stuff for remote sensors + tt->active=false; + tt->signal=1; + pinMode(pin,INPUT); // set mode to input + digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor + } if(v==1) WiFiCommand::print(""); return(tt); @@ -197,11 +199,17 @@ void Sensor::parse(char *c){ switch(sscanf(c,"%d %d %d",&n,&s,&m)){ case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH) - create(n,s,m,1); + if( n < REMOTE_SENSORS_FIRST_SENSOR ) + create(n,s,m,1); + else + WiFiCommand::print(""); break; case 1: // argument is a string with id number only - remove(n); + if( n < REMOTE_SENSORS_FIRST_SENSOR ) + remove(n); + else + WiFiCommand::print(""); break; case -1: // no arguments @@ -235,12 +243,14 @@ void Sensor::store(){ tt=firstSensor; EEStore::eeStore->data.nSensors=0; - while(tt!=NULL){ - EEPROM.put(EEStore::pointer(),tt->data); - EEStore::advance(sizeof(tt->data)); + while(tt!=NULL) { + if( tt->data.pin >= 0 ) { // don't store remote sensors + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + EEStore::eeStore->data.nSensors++; + } tt=tt->nextSensor; - EEStore::eeStore->data.nSensors++; - } + } } /////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_NodeMCU/Sensor.h b/DCCpp_NodeMCU/Sensor.h index aed60ee..d3aec6b 100644 --- a/DCCpp_NodeMCU/Sensor.h +++ b/DCCpp_NodeMCU/Sensor.h @@ -16,7 +16,7 @@ Part of DCC++ BASE STATION for the Arduino struct SensorData { int snum; - byte pin; + signed char pin; // modified to allow negative values as a flag for remote sensors byte pullUp; }; diff --git a/DCCpp_NodeMCU/WiFiCommand.cpp b/DCCpp_NodeMCU/WiFiCommand.cpp index fd23451..bcab2b6 100644 --- a/DCCpp_NodeMCU/WiFiCommand.cpp +++ b/DCCpp_NodeMCU/WiFiCommand.cpp @@ -19,6 +19,7 @@ Part of DCC++ BASE STATION for the Arduino #include "DCCpp_NodeMCU.h" #include "WiFiCommand.h" #include "Sensor.h" +#include "RemoteSensor.h" #include "EEStore.h" WiFiServer server(ETHERNET_PORT); @@ -152,7 +153,7 @@ void WiFiCommand::parse(char *com){ * returns: series of status messages that can be read by an client to determine status of DCC++ Base Station and important settings */ WiFiCommand::print(""); - WiFiCommand::print(" Date: Sat, 20 Apr 2019 12:38:50 -0400 Subject: [PATCH 7/7] Outputs added GPIO pins can now be defined as outputs --- DCCpp_NodeMCU/EEStore.cpp | 6 +- DCCpp_NodeMCU/Outputs.cpp | 257 ++++++++++++++++++++++++++++++++++ DCCpp_NodeMCU/Outputs.h | 39 ++++++ DCCpp_NodeMCU/WiFiCommand.cpp | 9 +- 4 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 DCCpp_NodeMCU/Outputs.cpp create mode 100644 DCCpp_NodeMCU/Outputs.h diff --git a/DCCpp_NodeMCU/EEStore.cpp b/DCCpp_NodeMCU/EEStore.cpp index 25bceba..dee261c 100644 --- a/DCCpp_NodeMCU/EEStore.cpp +++ b/DCCpp_NodeMCU/EEStore.cpp @@ -10,6 +10,7 @@ Part of DCC++ BASE STATION for the Arduino #include "DCCpp_NodeMCU.h" #include "EEStore.h" #include "Sensor.h" +#include "Outputs.h" #include /////////////////////////////////////////////////////////////////////////////// @@ -30,6 +31,8 @@ void EEStore::init(){ reset(); // set memory pointer to first free EEPROM space Sensor::load(); // load sensor definitions + Output::load(); // load output definitions + } /////////////////////////////////////////////////////////////////////////////// @@ -52,7 +55,8 @@ void EEStore::clear(){ void EEStore::store(){ reset(); EEPROM.begin( 4096 ); - Sensor::store(); + Sensor::store(); + Output::store(); EEPROM.put(0,eeStore->data); EEPROM.commit(); EEPROM.end(); diff --git a/DCCpp_NodeMCU/Outputs.cpp b/DCCpp_NodeMCU/Outputs.cpp new file mode 100644 index 0000000..a8e3e42 --- /dev/null +++ b/DCCpp_NodeMCU/Outputs.cpp @@ -0,0 +1,257 @@ +/********************************************************************** + +Outputs.cpp +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ +/********************************************************************** + +DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes. +Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW. +However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH. + +Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up. +The default is to set each defined pin to active or inactive according to its restored state. +However, the default behavior can be modified so that any pin can be forced to be either active or inactive +upon power-up regardless of its previous state before power-down. + +To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete +output definitions using the following variation of the "Z" command: + + : creates a new output ID, with specified PIN and IFLAG values. + if output ID already exists, it is updated with specificed PIN and IFLAG. + note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW + according to IFLAG value specifcied (see below). + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of output ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined output pins + returns: for each defined output pin or if no output pins defined + +where + + ID: the numeric ID (0-32767) of the output + PIN: the arduino pin number to use for the output + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows: + + IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW) + 1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH) + + IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending + on state before power-down; state of pin set to INACTIVE when first created + 1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE + depending on IFLAG, bit 2 + + IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created + 1 = state of pin set to ACTIVE uponm power-up or when first created + +Once all outputs have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the output definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +To change the state of outputs that have been defined use: + + : sets output ID to either ACTIVE or INACTIVE state + returns: , or if turnout ID does not exist + +where + + ID: the numeric ID (0-32767) of the turnout to control + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + +When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so +that it is retained even without power. A list of the current states of each output in the form is generated +by this sketch whenever the status command is invoked. This provides an efficient way of initializing +the state of any outputs being monitored or controlled by a separate interface or GUI program. + +**********************************************************************/ + +#include +#include "Config.h" +#include "DCCpp_NodeMCU.h" +#include "Outputs.h" +#include "EEStore.h" +#include "WiFiCommand.h" +#include + +/////////////////////////////////////////////////////////////////////////////// + +void Output::activate(int s){ + data.oStatus=(s>0); // if s>0, set status to active, else inactive + digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) + if(num>0) + EEPROM.put(num,data.oStatus); + WiFiCommand::print(""); + else + WiFiCommand::print(" 1>"); +} + +/////////////////////////////////////////////////////////////////////////////// + +Output* Output::get(int n){ + Output *tt; + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +void Output::remove(int n){ + Output *tt,*pp; + + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput); + + if(tt==NULL){ + WiFiCommand::print(""); + return; + } + + if(tt==firstOutput) + firstOutput=tt->nextOutput; + else + pp->nextOutput=tt->nextOutput; + + free(tt); + + WiFiCommand::print(""); +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::show(int n){ + Output *tt; + + if(firstOutput==NULL){ + WiFiCommand::print(""); + return; + } + + for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ + WiFiCommand::print("data.id); + if(n==1){ + WiFiCommand::print(" "); + WiFiCommand::print(tt->data.pin); + WiFiCommand::print(" "); + WiFiCommand::print(tt->data.iFlag); + } + if(tt->data.oStatus==0) + WiFiCommand::print(" 0>"); + else + WiFiCommand::print(" 1>"); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::parse(char *c){ + int n,s,m; + Output *t; + + switch(sscanf(c,"%d %d %d",&n,&s,&m)){ + + case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) + t=get(n); + if(t!=NULL) + t->activate(s); + else + WiFiCommand::print(""); + break; + + case 3: // argument is string with id number of output followed by a pin number and invert flag + create(n,s,m,1); + break; + + case 1: // argument is a string with id number only + remove(n); + break; + + case -1: // no arguments + show(1); // verbose show + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::load(){ + struct OutputData data; + Output *tt; + + for(int i=0;idata.nOutputs;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.id,data.pin,data.iFlag); + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + tt->num=EEStore::pointer(); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::store(){ + Output *tt; + + tt=firstOutput; + EEStore::eeStore->data.nOutputs=0; + + while(tt!=NULL){ + tt->num=EEStore::pointer(); + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextOutput; + EEStore::eeStore->data.nOutputs++; + } + +} +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::create(int id, int pin, int iFlag, int v){ + Output *tt; + + if(firstOutput==NULL){ + firstOutput=(Output *)calloc(1,sizeof(Output)); + tt=firstOutput; + } else if((tt=get(id))==NULL){ + tt=firstOutput; + while(tt->nextOutput!=NULL) + tt=tt->nextOutput; + tt->nextOutput=(Output *)calloc(1,sizeof(Output)); + tt=tt->nextOutput; + } + + if(tt==NULL){ // problem allocating memory + if(v==1) + WiFiCommand::print(""); + return(tt); + } + + tt->data.id=id; + tt->data.pin=pin; + tt->data.iFlag=iFlag; + tt->data.oStatus=0; + + if(v==1){ + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + WiFiCommand::print(""); + } + + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::firstOutput=NULL; + diff --git a/DCCpp_NodeMCU/Outputs.h b/DCCpp_NodeMCU/Outputs.h new file mode 100644 index 0000000..1748bce --- /dev/null +++ b/DCCpp_NodeMCU/Outputs.h @@ -0,0 +1,39 @@ +/********************************************************************** + +Outputs.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include "Arduino.h" + +#ifndef Outputs_h +#define Outputs_h + +struct OutputData { + byte oStatus; + int id; + byte pin; + byte iFlag; +}; + +struct Output{ + static Output *firstOutput; + int num; + struct OutputData data; + Output *nextOutput; + void activate(int s); + static void parse(char *c); + static Output* get(int); + static void remove(int); + static void load(); + static void store(); + static Output *create(int, int, int, int=0); + static void show(int=0); +}; // Output + +#endif + + diff --git a/DCCpp_NodeMCU/WiFiCommand.cpp b/DCCpp_NodeMCU/WiFiCommand.cpp index bcab2b6..d204707 100644 --- a/DCCpp_NodeMCU/WiFiCommand.cpp +++ b/DCCpp_NodeMCU/WiFiCommand.cpp @@ -19,6 +19,7 @@ Part of DCC++ BASE STATION for the Arduino #include "DCCpp_NodeMCU.h" #include "WiFiCommand.h" #include "Sensor.h" +#include "Outputs.h" #include "RemoteSensor.h" #include "EEStore.h" @@ -70,8 +71,12 @@ void WiFiCommand::process(){ if (!client[i] || !client[i].connected()) { if (client[i]) { client[i].stop(); + Serial.print("Stop client "); + Serial.println( i ); } client[i] = server.available(); + Serial.print("Connect client "); + Serial.print( i ); continue; } } @@ -85,7 +90,7 @@ void WiFiCommand::process(){ if(c=='<') // start of new command sprintf(commandString[i],""); else if(c=='>') { // end of new command -// Serial.print(commandString); + Serial.print(commandString[i]); parse(commandString[i]); } else if(strlen(commandString[i])"); // nothing defined for now + Output::parse(com+1); break; case 'S':