DCC++ Wi-Fi Receiver Trackside or Onboard

Simon Mitchell Oct 30, 2016

  1. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon,

    Not sure what you mean "I think the strtok solution may work"

    The strtok solution does work, just not on your platform :)

    So, I've been trying to find out why.
    Running the sketch on Uno produces the same problems you are having.
    A couple of things I can think of are 1. The compiler. 2. The serial chip on the target device.

    If the function exits even momentarily while waiting for serial to arrive, then it all falls apart.
    Moving local variables up to global status solves that issue on the Uno.

    The compilers and I mean it in the plural can also cause issues. What works on my version of the ESP compiler does not necessarily work on yours.
    I got the latest Git version of the ESP compiler this morning and 'sscanf' compiles fine. It doesn't actually work though, more work needed there.

    For now move

    char instring [23];
    byte ndx = 0;

    from the function, to the top of the sketch as globals, and try again. If still no joy the consider updating the compiler.

    Update: With the latest Git version compiler, and some good 'sscanf' documentation I now have it working on the ESP :)
     
    Last edited: Jan 18, 2017
    Scott Eric Catalano likes this.
  2. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Simon,

    Here's a neat version of strtok() parsing with nothing extra other than a 10 second message.

    One feature of this method is that it's not fussy about slight differences in the format. See below.
    I hope this works :) We'll move on to sscanf() once you got this figured.

    Code:
    DCC++ Command Parsing Test ESP8266
    
    DCC++ Command Parsing Test ESP8266
    
    <t 10 12345 126 1 >
     thisReg 10 thisLoco 12345 thisSpeed 126 thisDir 1
    DCC++ Command Parsing Test ESP8266
    
    <t10 12345 126 1>
     thisReg 10 thisLoco 12345 thisSpeed 126 thisDir 1
    DCC++ Command Parsing Test ESP8266
    
    < t10 12345 126 1 >
     thisReg 10 thisLoco 12345 thisSpeed 126 thisDir 1
    DCC++ Command Parsing Test ESP8266
    
    Code:
    uint16_t interval = 10000;
    
    uint32_t previousMillis = 0;
    char instring [23];
    byte ndx = 0;
    
    
    void parseCmdString() {
    
      while (Serial.available() > 0) {
        
        char inChar = (char)Serial.read();
        instring[ndx++] = inChar;   
        if (inChar == '>') {
          instring[ndx] = '\0';     
          ndx = 0;
    
          char delimiters[] = "<t >\r\n";
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};
    
          for (int i = 0; i < 4; i++) {
            tndx[i] = atoi(valPosition);
            valPosition = strtok(NULL, delimiters);
            if (i == 3) {
              byte thisReg = tndx[0];
              int thisLoco = tndx[1];
              byte thisSpeed = tndx[2];
              byte thisDir = tndx[3];
              //do something with the values
    
              Serial.print(" thisReg ");
              Serial.print(thisReg);
              Serial.print(" thisLoco ");
              Serial.print(thisLoco);
              Serial.print(" thisSpeed ");
              Serial.print(thisSpeed);
              Serial.print(" thisDir ");
              Serial.println(thisDir);
              yield();
            }
          }
        }
      }
    }
    
    
    
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
      Serial.println("DCC++ Command Parsing Test ESP8266");
      Serial.println();
    }
    
    void loop() {
    
      parseCmdString();
    
      if (millis() - previousMillis >= interval) {
    
        previousMillis = millis();
        Serial.println("DCC++ Command Parsing Test ESP8266");
        Serial.println();
      }
    }
     
    Scott Eric Catalano likes this.
  3. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    By that I meant the issue for function commands, where I haven't worked out how to handle a null field at tndx[3]. The full option for F13 - F28 is <f 5565 222 255>, but F0-F4 is only <f 5565 128>. Plan for today is to try and knock that over. I'm thinking if(tndx[2] > 221) . The tndx[2] value for F0-F8 is 128 min to 191 max, 222 for F13-F20 and 223 for F21-F28.

    Moving

    char instring [23];
    byte ndx = 0;

    to global fixed it, now working beautifully!

    I know very little about this, just stumbling around in the dark. I followed the readme from https://github.com/esp8266/Arduino and did the boards manager install, which I think does auto updates.
     
    Scott Eric Catalano likes this.
  4. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon

    Here's a curve ball for ya.

    There's an Arduino function called .parseInt()
    Basically grabs the the ascii chars as they come through the door, if they are numbers it converts them and you can then assign to a variable.
    I've used this to great effect in my designs, so I've not put much extra work in here :)

    Anyways, it turns out you can attach a wifi/tcp client to it just the same as serial.

    And with a little clever configuration, write a simple parser for all the Command types you would need on your loco 'decoder'.

    I'm not revealing all here, need to leave you something to do :) So, the clue (after you do plain Serial testing) is
    'tcpClient.parseInt()'
    'tcpClient.read()'

    Have fun :) This is a fully functional sketch, no mods needed for testing on serial.
    Code:
    uint16_t data1;
    uint16_t data2;
    uint16_t data3;
    uint16_t data4;
    uint16_t interval = 10000;
    uint32_t previousMillis = 0;
    
    void processCommand() {
    
      while (Serial.available()) {
    
        data1 = Serial.parseInt();//Does <1> and <0>
        if (Serial.read() == '>' && data2 == 0) {
          if (data1 == 1) Serial.print("Power On: ");
          if (data1 == 0) Serial.print("Power Off: ");
          Serial.print(data1);
          Serial.println();
          data1 = 0;
          break;
        }
        data2 = Serial.parseInt();//Does 2 byte f cmds
        if (Serial.read() == '>' && data3 == 0) {
          Serial.print("Function Cmd - Loco: ");
          Serial.print(data1);
          Serial.print(" 1st Byte: ");
          Serial.print(data2);
          Serial.println();
          //Pass to Function Process then clear down as below
          data1 = 0;
          data2 = 0;
          break;
        }
        data3 = Serial.parseInt();//Does 3 byte f cmds
        if (Serial.read() == '>' && data4 == 0) {
          Serial.print("Function Cmd - Loco: ");
          Serial.print(data1);
          Serial.print(" 1st Byte: ");
          Serial.print(data2);
          Serial.print(" 2nd Byte: ");
          Serial.print(data3);
          Serial.println();
          //Pass to Function Process then clear down as below
          data1 = 0;
          data2 = 0;
          data3 = 0;
          break;
        }
        data4 = Serial.parseInt();//Does 4 int t cmds
        if (Serial.read() == '>') {
          Serial.print("Throttle Cmd - Reg: ");
          Serial.print(data1);
          Serial.print(" Loco: ");
          Serial.print(data2);
          Serial.print(" Speed: ");
          Serial.print(data3);
          Serial.print(" Dir: ");
          Serial.print(data4);
          Serial.println();
          //Pass to Throttle Process then clear down as below
          data1 = 0;
          data2 = 0;
          data3 = 0;
          data4 = 0;
          break;
        }
      }
    }
    
    void setup() {
      Serial.begin(115200);
    }
    
    void loop() {
    
      processCommand();
    
      if (millis() - previousMillis >= interval) {
        previousMillis = millis();
        Serial.println("Command Parsing Test Esp8266");
        Serial.println();
      }
    }
    
    
    
     
    Scott Eric Catalano likes this.
  5. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    haha. Gold. Just as I get my head around this method and get it to work! this seems all very happy, it crashes if there is an incomplete string sent, eg <t 1 5565 123> (no dir included), but happily handles multiple commands and leading spaces. It is also nice and quick
    Code:
    char instring [23];         //command string creation
    byte ndx = 0;               //command string index
    
    int nCABAddr = 5565;        // this cab addr
    byte nSpeed = 0;            // throttle command from ndx[4]
    byte nDir = 0;              // throttle command from ndx[5]
    byte eByte = 0;             // function command F0-F12
    byte fByte = 0;             // function command F13-F28
    
    
    void parseCmdString() {
    
      while (Serial.available() > 0) {
    
        uint32_t startTime = millis();
        char inChar = (char)Serial.read();    //read data from serial
        instring[ndx++] = inChar;             //add data to string
        if (inChar == '>') {                  // '>' is the terminating character for a DCC++ command
          instring[ndx] = '\0';               // terminating character added to the string
          ndx = 0;                            // reset ndx to 0 for the next string
    
          Serial.println(instring);             //print the raw string for de-bugging
          yield();                              //get out of the way for WiFi connectivity
          char delimiters[] = "< >\r\n";        //working delimiters, dropped t
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};            //4 x int.
    
          if (strchr(instring, 't')) {      // throttle command format <t reg CAB Speed Dir>
            for (int i = 0; i < 5; i++) {   //increased from 4 to 5, as char 't' not a delimiter
              tndx[i] = atoi(valPosition);
              valPosition = strtok(NULL, delimiters);
              if (i == 4 && tndx[2] == nCABAddr) {//when all fields are allocated and check command is for this CAB
                nSpeed = tndx[3];            // gets index 4
                nDir = tndx[4];              // gets index 5
                Serial.print("Throttle Command");
                Serial.print(" nCABAddr ");
                Serial.print(nCABAddr);
                Serial.print(" nSpeed ");
                Serial.print(nSpeed);
                Serial.print(" nDir ");
                Serial.println(nDir);
                Serial.print("Time: ");
                Serial.print(millis() - startTime);
                Serial.println(" ms");
                yield();
              }
            }       //for
          } //Throttle Command
          else if (strchr(instring, 'f')) {      // function command format <f CAB eByte fByte>
            for (int i = 0; i < 3; i++) {
              tndx[i] = atoi(valPosition);
              valPosition = strtok(NULL, delimiters);
              if (i == 2 && tndx[1] == nCABAddr) { //stop iterating before null field and check command is for this CAB
                eByte = tndx[2];            // gets index 3
                Serial.print("Function Command");
                Serial.print(" nCABAddr ");
                Serial.print(nCABAddr);
                Serial.print(" eByte ");
                Serial.print(eByte);
                yield();
                if (tndx[2] > 221) {                 // fByte can be null, but is only invoked for eByte 222 or 223
                  tndx[3] = atoi(valPosition);
                  valPosition = strtok(NULL, delimiters);
                  fByte = tndx[3];              // gets index 4, may be NULL
                  Serial.print(" fByte ");
                  Serial.println(fByte);
                  Serial.print("Time: ");
                  Serial.print(millis() - startTime);
                  Serial.println(" ms");
                }
                else
                {
                  Serial.println(" fByte NULL ");
                  Serial.print("Time: ");
                  Serial.print(millis() - startTime);
                  Serial.println(" ms");
                }
              }
            }//for
          }//function command
          else
          {
            Serial.print ("invalid Command");     //stop the stack crashing
          }
        }//string complete
      }//serial.available;
    }//parseCmdString();
    
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
    }
    
    void loop() {
    
      parseCmdString();
    }
    
     
    Scott Eric Catalano likes this.
  6. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Good work, and all helps with the learning.

    The .parseInt might not cover all bases, but it's another avenue to look at.

    With all the test printing stripped out, it would be quite compact too.

    We don't need to add sscanf in just yet then? :)

    Here's some good reading on that btw. http://docs.roxen.com/pike/7.0/tutorial/strings/sscanf.xml

    Time out for me.:sleep:
     
    Scott Eric Catalano likes this.
  7. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    That .parseInt sketch worked so nicely out of the box. However given that JMRI could be used to write decoder values/poll sensors etc which use the same size commands, I think it might be better to stick with the strtok method, which now works sweetly. I'm going to crack on with moving that into my 'clean sheet' sketch, and start doing the decoder and motor drive functions.

    After some consideration, I've decided not to do braking and momentum on the decoder, as my downstream options could be a computer controlled fiddle yard. And that might need a quick stop by the CPTR, rather than a lovely momentum driven crash!! so my JMRI throttle project is going to be a belter once I get to it.
     
    Scott Eric Catalano likes this.
  8. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    I can appreciate there would be a need to avoid different type commands using my example of parseInt().

    Which leads on to the greater problem of how the whole WiFi node type system is going to be managed.

    For sure you can hook up dozens of ESP's to a master router, but how do you target each node? Will it be a send to all type system?
    With each 'decoder' doing the filtering as in DCC. Or a targeted system where you use the IP address as the filter?
    And what of the code or API's to do this? Pointing JMRI at 30 IP's is not something it does out of the box or is it :)

    That's the real challenge of such a system, and the one I was wary of when you first announced the project.

    I look forward to seeing how you will solve such ideas.

    Steve.
     
  9. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Another day spent learning useful stuff. LED and SERVO functions ported across and tested. Have spent some time trying to get the firebox LED to glow bright whilst under way. I can get it working in loop, but would prefer it was controlled (e.g. f4), but when I moved it out to the function void and tried to make a 'while' statement, the stack crashed. I'm seeing this whenever the result is outside a stated case. I'll work it out tomorrow!

    My game plan is for each ESP8266 to act as a Decoder and do the filtering, via "nCABAddr". I think JMRI can handle multiple 'connections'. Not the most elegant way of doing things. So I'm finishing this off because I started it(!)

    I think I'll go with the Deltang (or Deltino) option shortly. Not much to change, Robin_2 has done the work, and I've just added some capabilities. I didn't understand what he had done when I started out on this, I've got a better handle on it now.
     
    Scott Eric Catalano likes this.
  10. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    Umm... my name is not Steve, it's Mark... but thank you! (I wrote both VSD and the JMRI side of the JMRI/DCC++ support)

    That said, Steve's probably forgotten more about DCC++ in general and the ESP8266 in particular than I'll ever know, so he's your man :)

    Very interesting project you have going here, and I'm very glad to see you are making plenty of forward progress. keep up the good work!!
     
    Scott Eric Catalano likes this.
  11. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Hi Mark,
    My apologies!!
    I'm really keen to use the VSD feature, so again, thanks for doing all that hard work!
     
    Scott Eric Catalano likes this.
  12. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    There's nothing wrong per se with doing some light work in loop().
    If your LED routine will run without slowing loop() considerably, then fine.
    Using stuff like delay() in such routines gives a risk of missing that next important command.
    Use timers wherever you can, I've given examples in my code. That way loop() can keep running.

    Well written functions on a single threaded device can give the illusion of multi-tasking.

    Here's a short introduction of the concept https://learn.adafruit.com/multi-tasking-the-arduino-part-1/using-millis-for-timing
     
    Scott Eric Catalano likes this.
  13. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I tried a few things with the firebox glow in void functions(), but it kept crashing. So I included the F4 test in loop(), and all seems happy enough, so meh. I'd actually prefer a constant light out the front of the ashpan (dampers), and a partially open firebox door. I could do this via analogWrite(LED2, val), but I'm happy with where I am at. I'm still prototyping, so lots of serial.print everywhere. Tomorrow I will do the motor control, and then maybe get on with physical testing blinking lights, servos and motors.

    Code:
    
    /*
        DCCpp_ESPv1.ino
    
    
        Copyright (c) 2017 Simon Mitchell.
    
        Licensed under the EUPL, Version 1.1 only (the "Licence");
        You may not use this work except in compliance with the Licence.
        You may obtain a copy of the Licence at:
    
          http://joinup.ec.europa.eu/software/page/eupl
    
        Unless required by applicable law or agreed to in writing, software
        distributed under the Licence is distributed on an "AS IS" basis,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
        See the Licence for the specific language governing permissions and
        limitations under the Licence.
    
      ===============================
        This licence is similar to the GNU General Public License v.2,  the
          Eclipse Public License v. 1.0 etc.
      ===============================
    
    */
    #include <ESP8266WiFi.h>      //specific to the ESP8266 board, eg WeMos D1 Mini
    #include <Servo.h>
    
    //===================================================================================
    // WiFi Communication
    IPAddress ip(192, 168, 54, 65);                     //DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
    IPAddress gateway(192, 168, 54, 1);                 //SET GATEWAY TO MATCH YOUR NETWORK
    IPAddress subnet(255, 255, 255, 0);                 //SET SUBNET MASK TO MATCH YOUR NETWORK
    
    #define ETHERNET_PORT 2560                          // DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE
    #define MAC_ADDRESS {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
    
    const char* ssid     = "Bradford (Exchange)";       // Wi-Fi login
    const char* password = "WestY0rksh!re1954";
    
    /* WiFi Socket Server and Client Class instance */
    WiFiServer server(ETHERNET_PORT);                   // Create and instance of an WIFIServer
    WiFiClient Client;                                  // Create instance of an WIFIClient.
    
    //===================================================================================
    //DCC++ Communications and Configurations
    char instring [23];         // command string creation
    byte ndx = 0;               // command string index
    int DEFAULTCAB = 3;         // DEFINE DEFAULT CAB ADDRESS
    int nCABAddr = 5565;        // CAB:  the short (1-127) or long (128-10293) address of the engine decoder, will store this in eeprom again later
    byte nSpeed = 0;            // throttle command from ndx[4]
    byte nDir = 0;              // throttle command from ndx[5]
    byte fByte = 0;             // function command F0-F12
    byte eByte = 0;             // function command F13-F28
    uint32_t startTime = 0;
    uint32_t waitUntil = 0;
    int ledState = HIGH;    //ledState used to set the firebox LED, HIGH is actually off
    
    
    //===================================================================================
    //PIN Definitions
    #define LED0      D6 //Front lamp
    #define LED1      D7 //Rear lamp
    #define LED2      D0 //Firebox (D8 didn't allow the re-write to happen)
    #define SERVOFRONT        D3
    #define SERVOREAR         D4
    #define SERVOREVERSER     D5
    
    //===================================================================================
    // Servo Motor control instance
    Servo Front_CouplerServo;  // (Front Coupler)
    Servo Rear_CouplerServo;  // (Rear Coupler)
    Servo ReverserServo;  // (Reverser)
    
    // Initial Positions of Servo Motors
    unsigned short nFrontPos = 90;
    unsigned short nRearPos = 90;
    unsigned short nReverPos = 90;
    
     
    Scott Eric Catalano likes this.
  14. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Code:
    //===================================================================================
    void functions()
    {
      /*
            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
      */
      // this is a request for functions FL,F1-F8
      if ((fByte & 0xE0) == 128)
      { // 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16
        Serial.println("F0-F4 Command");
        /*
            If F0 (Lights) is on
            Enable LED if DIRECTION = 1 (Forward)
            Enable LED if DIRECTION = 0 (Reverse)
            Enable LED and if SPEED>0, then brighten for 20s every 120s (firebox)
        */
        // F0 Command process
        if (bitRead(fByte, 4))
        {
          if (nDir) { // Direction 1
            digitalWrite(LED0, LOW); //WeMOS D1Mini LOW is LED on, HIGH is off
            digitalWrite(LED1, HIGH);//WeMOS D1Mini LOW is LED on, HIGH is off
          }
          if (nDir == 0) { // Direction 0
            digitalWrite(LED0, HIGH);//WeMOS D1Mini LOW is LED on, HIGH is off
            digitalWrite(LED1, LOW);//WeMOS D1Mini LOW is LED on, HIGH is off
          }
          Serial.println("F0 Enable LED");
        }
        else
        {
          digitalWrite(LED0, HIGH);//WeMOS D1Mini LOW is LED on, HIGH is off
          digitalWrite(LED1, HIGH);//WeMOS D1Mini LOW is LED on, HIGH is off
          Serial.println("F0 Disable LED");
        }
        //F2 (Front Coupler) Command Process
        /*On - Control servo to decrease to 45 degrees
          Off– Control servo to return to 90 position*/
        if (bitRead(fByte, 1) && nFrontPos > 45)
        {
          for (nFrontPos = 90; nFrontPos >= 45; nFrontPos -= 1)
          {
            Front_CouplerServo.write(nFrontPos);
            Serial.print("F2 Front servo Pos:");
            Serial.println(nFrontPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
        if (bitRead(fByte, 1) == 0 && nFrontPos < 90)
        {
          for (nFrontPos = 45; nFrontPos <= 90; nFrontPos += 1)
          {
            Front_CouplerServo.write(nFrontPos);
            Serial.print("F2 Front servo Pos:");
            Serial.println(nFrontPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
        //F3 (Rear Coupler) Command Process
        /*  On - Control servo to decrease to 45 degrees
            Off – Control servo to return to 90 position*/
        if (bitRead(fByte, 2) && nRearPos > 45)
        {
          for (nRearPos = 90; nRearPos >= 45; nRearPos -= 1)
          {
            Rear_CouplerServo.write(nRearPos);
            Serial.print("F3 Rear servo Pos:");
            Serial.println(nRearPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
        if (bitRead(fByte, 2) == 0 && nRearPos < 90)
        {
          for (nRearPos = 45; nRearPos <= 90; nRearPos += 1)
          {
            Rear_CouplerServo.write(nRearPos);
            Serial.print("F3 Rear servo Pos:");
            Serial.println(nRearPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
        //F4 (Firebox) Command Process
        /*  treated as stand alone as this is visible at all times*/
        if (bitRead(fByte, 3))  Serial.println("F4 Automatic Fireman installed!");
        /*while (bitRead(fByte, 3))
          {
          if (nSpeed > 0) { // Firebox, fire for 20s every 2 mins
            if (millis() - startTime >= waitUntil) {
              if (ledState == HIGH) {
                ledState = LOW;
                startTime = millis();
                waitUntil = 4000;    //on for 2 sec
                Serial.println("Firing");
                yield();
              }
              else
              {
                ledState = HIGH;
                startTime = millis();
                waitUntil = 15000;  //off for 10 sec
                Serial.println("Firebox door closed");
                yield();
              }
            }
          }
          else
          {
            ledState = HIGH;
            //Serial.println("Firebox door closed");
          }
          digitalWrite(LED2, ledState);
          }*/
      }//end Fl, F0-F4 Commands
      // this is a request for functions F9-F12, which in this case control a servo for Walschearts Valve Gear
      if ((fByte & 0xF0) == 160)
      {
        Serial.println("F9-F12 Command");
    
        // F9-F12 Command Process (reverse gear = 120)
        if (nReverPos > map(fByte, 160, 175, 62, 122))
        {
          for ( ; nReverPos >= map(fByte, 160, 175, 62, 122); nReverPos -= 1)
          {
            ReverserServo.write(nReverPos);
            Serial.print("F9-F12 Reverser Reduce cut-off:");
            Serial.println(nReverPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
        else if (nReverPos < map(fByte, 160, 175, 62, 122))
        {
          for ( ; nReverPos <= map(fByte, 160, 175, 62, 122); nReverPos += 1)
          {
            ReverserServo.write(nReverPos);
            Serial.print("F9-F12 Reverser Increase Cut-off:");
            Serial.println(nReverPos);
            millis() + 30;
            yield();                   //get out of the way for WiFi connectivity
          }
        }
      }//End F9-F12 Commands
    
      // this is a request for functions F13-F20, used to emulate Momentum without writing to eeprom. Still to be re-written
      if (fByte == 222)
      { // F13 - F20
        Serial.println("F13-F20 Command");
        // F13 command check
        /* If F13 Momentum_Value received ie < f 03 222 BYTE2 >, map(F13,0,255,0,200), */
        if (bitRead(eByte, 0))
        {
          Serial.print("Got any smart ideas?");
        }
        //this is a request for functions F21-F28, used to emulate Braking without writing to eeprom Still to be re-written
    
        if (fByte == 223)
        { // F21 - F28
          Serial.println("F21-F28 Command");
          // F21 command check
          /* If F21 Brake_Value received ie < f 03 223 BYTE2 >, map(F21,0,255,0,200), reduce motor speed using delay(Brake_Value) */
          if (bitRead(eByte, 0))
          {
            Serial.print("Got any other smart ideas?");
          }
        }
      }
    }
    
     
    Scott Eric Catalano likes this.
  15. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Code:
    //===================================================================================
    void parseCmdString() {
      while (Client.available() > 0) {
    
        uint32_t startTime = millis();
        char inChar = (char)Client.read();    //read data from client
        instring[ndx++] = inChar;             //add data to string
        if (inChar == '>') {                  // '>' is the terminating character for a DCC++ command
          instring[ndx] = '\0';               // terminating character added to the string
          ndx = 0;                            // reset ndx to 0 for the next string
    
          Serial.println(instring);             //print the raw string for de-bugging
          yield();                              //get out of the way for WiFi connectivity
          char delimiters[] = "< >\r\n";        //working delimiters,
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};            //4 x int.
    
          if (strchr(instring, 't')) {      // throttle command format <t reg CAB Speed Dir>
            for (int i = 0; i < 5; i++) {   // throttle commands should be 4 int long
              tndx[i] = atoi(valPosition);  // convert to int
              valPosition = strtok(NULL, delimiters);
              if (i == 4 && tndx[2] == nCABAddr) {//when all fields are allocated and check command is for this CAB
                nSpeed = tndx[3];            // gets index 4
                nDir = tndx[4];              // gets index 5
                Serial.print("Throttle Command");
                Serial.print(" nCABAddr ");
                Serial.print(nCABAddr);
                Serial.print(" nSpeed ");
                Serial.print(nSpeed);
                Serial.print(" nDir ");
                Serial.println(nDir);
                Serial.print("Time: ");
                Serial.print(millis() - startTime);
                Serial.println(" ms");
                yield();
              }
            }       //for
          } //Throttle Command
          else if (strchr(instring, 'f')) {      // function command format <f CAB eByte fByte>
            for (int i = 0; i < 3; i++) {        // function commands should be 3 to 4 int long
              tndx[i] = atoi(valPosition);
              valPosition = strtok(NULL, delimiters);
              if (i == 2 && tndx[1] == nCABAddr) { //stop iterating before possible fByte null field and check command is for this CAB
                fByte = tndx[2];            // gets index 3
                Serial.print("Function Command");
                Serial.print(" nCABAddr ");
                Serial.print(nCABAddr);
                Serial.print(" fByte ");
                Serial.print(fByte);
                yield();
                if (tndx[2] > 221) {                 // fByte can be null, but is only invoked for eByte 222 or 223
                  tndx[3] = atoi(valPosition);       // go on to obtain fByte
                  valPosition = strtok(NULL, delimiters);
                  eByte = tndx[3];              // gets index 4, may be NULL
                  Serial.print(" eByte ");
                  Serial.println(eByte);
                  Serial.print("Time: ");
                  Serial.print(millis() - startTime);
                  Serial.println(" ms");
                  functions();
                }
                else
                {
                  Serial.println(" eByte NULL "); //stop the stack crashing!
                  Serial.print("Time: ");
                  Serial.print(millis() - startTime);
                  Serial.println(" ms");
                  functions();
                }
              }
            }//for
          }//function command
          else
          {
            Serial.print ("invalid Command");     //stop the stack crashing
          }
        }//string complete
      }//serial.available;
    }//parseCmdString();
    //===================================================================================
    void serialCommandProcess() {
      //check if there are any new clients
      if (server.hasClient())
      {
        if (!Client || !Client.connected())
        {
          if (Client) Client.stop();
          Client = server.available();
          Serial.println("New client Connected");
        }
        else
        {
          WiFiClient serverClient = server.available();
          serverClient.stop();
        }
      }//server.hasClient()
    } // SerialCommandProcess()
    
    //===================================================================================
    void setup() {
    
      // Pin configuration
      pinMode(LED0, OUTPUT);
      digitalWrite(LED0, HIGH);
    
      pinMode(LED1, OUTPUT);
      digitalWrite(LED1, HIGH);
    
      pinMode(LED2, OUTPUT);
      digitalWrite(LED2, HIGH);  //see also 'ledState' in global decs
    
      pinMode(LED_BUILTIN, OUTPUT);
    
      Front_CouplerServo.attach(SERVOFRONT);
      Rear_CouplerServo.attach(SERVOREAR);
      ReverserServo.attach(SERVOREVERSER);
    
      Serial.begin(19200);            // configure serial interface
      Serial.println("\nSerial Begin 19200 Baud");
      Serial.flush();
      // WIFI Begin
      WiFi.config(ip, gateway, subnet);
      WiFi.begin(ssid, password);
      Serial.print("\nConnecting to ");
      Serial.println(ssid);
    
      uint8_t idx = 0;
      // If WIFI is not connected, 10 sec delay.
      while (WiFi.status() != WL_CONNECTED && idx++ < 20) delay(500);
      if (idx == 21)
      {
        Serial.print("Could not connect to ");
        Serial.println(ssid);
        while (1)
        { // When Wifi connection error, Led flickers per 0.5sec
          digitalWrite(LED_BUILTIN, LOW);
          millis() + 250;
          digitalWrite(LED_BUILTIN, HIGH);
          millis() + 250;
        }
      }
    
      // Server socket start.
      server.begin();
      server.setNoDelay(true);
    
      // Report Wi-Fi connection Status
      Serial.println("DCC++ ESP8266 Mobile Decoder is Ready!");
      Serial.print("Local IP is ");
      Serial.println(WiFi.localIP());
      Serial.print("Current CAB is ");
      Serial.println(nCABAddr);
    
    
    
    }// end setup
    //===================================================================================
    void loop()
    {
      serialCommandProcess();                 // check for new serial commands
      parseCmdString();                       // check for new serial commands
    
    
      //F4 (Firebox) Command Process, couldn't get this to work in void functions()
      /*  treated as stand alone as this is visible at all times*/
      if (bitRead(fByte, 3) && nSpeed > 0) { // Firebox, fire for 20s every 2 mins
        if (millis() - startTime >= waitUntil) {
          if (ledState == HIGH) {
            ledState = LOW;
            startTime = millis();
            waitUntil = 4000;    //on for 2 sec
            Serial.println("Firing");
            yield();
          }
          else
          {
            ledState = HIGH;
            startTime = millis();
            waitUntil = 15000;  //off for 10 sec
            Serial.println("Firebox door closed");
            yield();
          }
        }
      }
      else
      {
        ledState = HIGH;
        //Serial.println("Firebox door closed");
      }
      digitalWrite(LED2, ledState);
    
    }
    
     
    Scott Eric Catalano likes this.
  16. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Sorry folks, limit of 10,000 characters here. Does this mean its time to join Github?
     
    Scott Eric Catalano likes this.
  17. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Github? Nightmare, I still don't get it :eek:
     
  18. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    No worries, Simon. I hope the VSD stuff works out for you. It is very much hamstrung by a lack of source audio material, and I haven't figured out what to do about that yet.

    I may have missed... what scale is the loco you are putting this wireless decoder in?
     
    Scott Eric Catalano likes this.
  19. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Hi Mark, one potential source is the trainsim community. I've had a brief look but it's a little way off yet. I hope to add to that resource, albeit not being able to obtain original recordings myself.

    I'm working in O gauge, 7mm to the foot.
     
    Scott Eric Catalano likes this.
  20. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    I've poked around a bit in the train sim community before... maybe I just wasn't poking in the right places, but there didn't seem to be much interest.

    This project is making a bit more sense now... I was wondering how you would cram an Arduino in an HO or N scale loco... :)
     
    esfeld and Scott Eric Catalano like this.

Share This Page