DCC++ Wi-Fi Receiver Trackside or Onboard

Simon Mitchell Oct 30, 2016

  1. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Firstly, a huge vote of thanks to Gregg and Steve (TD) for their massive contribution to the hobby via DCC++. I've finally trawled through just about all of the DCC++ posts, including the 75 page introductory thread. My eyes are square and my brain is severely fogged. Unfortunately, I failed to find exactly what I was after, so to make this easy to find, I'm starting a new thread.

    I'd like to build a battery powered onboard w-fi controlled locomotive decoder. If this has already been done with DCC++ please point me in the right direction!

    Please excuse my plain language, I know next to nothing about Wi-Fi and coding, and any sketches I share will definitely be of the copy and paste variety. I'm also modelling in O gauge, so size constraints are less of a problem. I am planning under baseboard sound, so JMRI & DCC++ feature in my plans (thanks again Steve for your VSD project). A DCC++ base station does not feature in my plans, but I may be severely misguided in that.

    Design elements that I am looking for;
    1. Control via Wi-Fi, using the DCC++ commands
    2. Motor control
    3. Servo Control (front and rear un-couplers, outside valve gear reversing)
    4. Lights
    5. Speed sensing feedback/chuff detection for sound
    6. Battery Power (probably Li-po and probably charged through the track)
    The closest implementation that I have seen has been Dave Bodnar's DCC++ Receiver Trackside or Onboard
    However Dave's design uses the HC12 radio module, which IMHO would be great for home use, but I am concerned about it's use in an exhibition environment, and hence the preference for Wi-Fi protocol. I think the rest of the code has a lot of potential. I have emailed Dave, however is otherwise occupied, so I am going to take a crack at this myself.

    Selecting hardware at this early stage is probably getting ahead of myself, however I note Steve's work with the ESP8266 boards, and they may be ideal. I have ordered 2x;
    The battery shield is a suck it and see idea, mostly for compatability. 3.7v is probably less than ideal for my Mashima 1833 motors, but I want to do a proof of concept and look at the power supply/management in the next development stage.

    I'm also looking closely at the work done at DWiC especially with regard to the use of the i/o pins so as to have some consistency of standards.

    I see the decoder as having a DCC address, with a Wireless AP looking after the comms. In my case, probably NOT my RPi3, rather a dedicated and high powered access point, given that I could have 20+ loco's and 3 hand held hardware throttles. I think this will go beyond the capabilities of the RPi3.

    Grateful for any and all assistance!
     
    Scott Eric Catalano likes this.
  2. Pieter

    Pieter TrainBoard Member

    152
    46
    10
     
    Scott Eric Catalano likes this.
  3. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Pieter, I have had a good look on Martin's website and emailed him. I think we are heading in similar directions.
     
    Scott Eric Catalano likes this.
  4. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    @Simon,

    That is quite a wish list you have there.
    I must ask though, do you have a seriously valid reason for wanting to build such a system ?
    You are proposing such complex layers of functionality, and frankly I don't see the point.
    However that is not to say it's not doable.

    If as you admit, know little of how to code in C/C++ or the WiFi protocols, then you have a long road ahead of you.
    No one is going to write this for you, and if you go ahead, be prepared to put literally hundreds of hours of your spare time.
    By the end you may start to appreciate that silicon chips and talk are very cheap. Good Software is priceless !

    It's nice to see you've at least had a good look around for something that fits your requirements.
    Dave Bodnar's project is an excellent starting point. I advise that you build the project, and get to know the C code inside out.
    Develop it to better suit your needs.

    While you're doing that, do say 50 to 100 hours reading about the ESP8266. Build some ESP starter projects.
    Then when you have learned how to get 2 ESP's to communicate with each other, swap out the HC12's.

    When that is working, you can move on to designing the larger network and addressing protocols you would need for a multi loco setup.


    I've spent over 9 months getting to know the ESP8266, its a very capable device.
    Today I would see your proposals as a very ambitious project !

    Good luck.
     
  5. martan3d

    martan3d New Member

    1
    1
    6
    Just for the record, a more descriptive page specifically about my train controller can be found at: http://controlwidgets.com/

    It's a bit dated, I'm working on a second pass of the code and a refactor of my main PCB into a much smaller footprint but the main overall network design is the same.

    Martin
     
    Scott Eric Catalano likes this.
  6. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Steve and Martin.
    Steve your comments are very sobering. I am retired, so do have time available. Having said that, it is amazing how quickly the days go by!
    I have re-read Martin's website and will have a long and hard think about that. I note that JMRI supports x-bee but as far as I can see, only for accessory decoders.
     
    Scott Eric Catalano likes this.
  7. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    @Simon,

    You will be far better served going with ESP8266, for the simple reason it is a WiFi - Uart all in one, it has a 32 bit 80MHz programmable mcu, you can develop your code in the mega popular Arduino IDE and the chip has massive support in the online community.

    That will certainly give you a huge leg up, right from the off. What ever your application, it's almost nailed on that the folks out there have written the tools and libraries for you to put together your concept. But it will still require a lot of study on your part.
     
  8. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    That was my thinking Steve. I do have an old friend with lots of experience with the esp8266
     
    Scott Eric Catalano likes this.
  9. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    @Simon,

    That's great, a head start for you there then, look after that friend !

    It will be interesting to see how things progress, do ask if you get stuck with something.

    I can try pointing toward a solution.

    Websockets are an easy to use, serial style comms protocol. Do some googling and youtube. Build a few projects with your D1's

    Again good luck if you decide to go ahead.
     
    Scott Eric Catalano likes this.
  10. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Well I've had a fun couple of days making lights blink and servos move with the Arduino Uno.

    Using ESP8266_TcpPeripheral.ino , I've been able to get the WeMos D1 to connect to my WAP. Well at least it showed up as the IP Addr I set when I checked out my router. I didn't do anything smart like send it a command or anything. The rest of that sketch is too complex for me just yet. The WAP (Edimax BR-6428nC) is playing hide and seek. The SSID is visible and works, but I can't log in to edimax.setup nor the supposed default IP addr of 192.168.2.1. All very well when its plugged in to the network, but if I want it as a stand alone at an exhibition, I'll need that IP address..which I thought I re-set and wrote down, but that doesn't work either. So my network engineer friend will have to help me find that one. I'm also not sure if Oscar's sketch is more robust than I need...

    I've soldered up the WeMos D1 motor shield, and successfully run the "motor_base" example, referencing the library "WEMOS_Motor.h".

    So what I'd like to do next is crack the "Commands for DCC++ Base Station" which I'm pretty sure is what WiThottle/JMRI/DCC++ controllers will all be transmitting. I've been trying to work out what Dave B was doing, but thought I'd spotted a way of grabbing the chunk you want from the command. First I'll work on a throttle command for my star loco, Stanier Jubilee 4-6-0 45565 "Victoria", eg < t 1 5565 20 1 > and the reply < T 1 5565 20 1 >

    Cheers
     
    Scott Eric Catalano likes this.
  11. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Progress is slow. I'm getting myself nicely confused. So I thought I'd do up a specification of sorts. Please bear in mind I'm heading towards a custom steam throttle with a proportional reverser, a proportional momentum and proportional brake. Hence hijacking the F9-F12, F13-F20, F21-F28 ranges. I'm not too worried about sound triggers as I'm a ways off working on Virtual Sound Decoder.


    ESP8266 WeMos D1 Mini + Motor Shield Sketch Specification

    Enable Serial communication over WiFi
    • SSID (user edit in sketch)
    • Password (user edit in sketch)
    • #define IP_Address (user to comment out to use DHCP if not used)
    • #define ETHERNET_PORT 2560 (Default, user to comment out if not used)
    • #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }

    Action Throttle commands from JMRI only if they relate to this ‘CAB’ address
    • Store in EEPROM the ‘CAB’ address, value 1-10293 (example “03”, user to edit the sketch)
    • If the command string relates to that ‘CAB’ Address (example “03”);
    • Where;
      • The CAB throttle format is < t REGISTER CAB SPEED DIRECTION>
      • Example < t 1 03 20 1 >.
      • Breakdown for this example is:
    "<" = Listen to me I am the beginning of a DCC++ command. (A space after < is not required but acceptable)
    "t" = (lower case t) This command is for a Decoder installed in a engine or simply a "cab".
    "1" = REGISTER: (ignore)
    "03" = CAB: the short (1-127) or long (128-10293) address of the engine decoder (this has to be already programmed in the decoder) See Programming Commands bellow.
    "20" = SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0)
    "1" = DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train
    ">" = I am the end of this command

    o If the command was successful the serial monitor should reply with : meaning :
    "<" = Begin DCC++ command
    "T" = (upper case T) DCC++ Cab command was sent from DCC++ BaseStation
    "1" = register 1 was changed
    "20" = set to speed 20
    "1" = forward direction
    ">" = End DCC++ command​

    • Drive the WeMos Mini motor shield;
      • Change to DIRECTION per command
      • Change to SPEED (0-126) per command;
        • Subject to
          • delay(200) (user to modify as per CV)
          • +F13 Momentum_Value (value user entered in sketch as per a CV)
          • -F21 Brake_Value (treat as decimal, 0-255(?), decrease speed by this factor, this is emulating braking)

    Action CAB Functions

    • If the commands string relates to that ‘CAB’ Address (example “03”)
    • Where;
    • 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)
    • If F2 (Front Coupler) is
      • On - Control servo to increase by 45 degrees
      • Off– Control servo to return to 0 position
    • If F3 (Rear Coupler) is
      • On - Control servo to increase by 45 degrees
      • Off – Control servo to return to 0 position
    • If F9 Reverser_Value recieved ie < f 03 BYTE1 >, control servo to increase/decrease by 45 degrees
    • If F13 Momentum_Value received ie < f 03 222 BYTE2 >, map(F13,0,255,0,200),
    • If F21 Brake_Value received ie < f 03 223 BYTE2 >, map(F21,0,255,0,200), reduce motor speed using delay(Brake_Value)

    Monitor Current - I have not yet worked the ‘command’ format to report this back to JMRI.
     
    Scott Eric Catalano likes this.
  12. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Woot! I've got this responding to commands in PuTTY (I cheated and got some help). Next up is a bunch of bug squashing and finessing. And also getting it to actually move motors/servos and light LEDs, rather than just parrot back commands.

    It is in essence a mobile base station. Time will tell if this is a stupid way to do things (it probably is). Connecting via an IP address and port 2560.

    And I've managed to freeze up my JMRI on RPi3, due to a failed connection. I've looked for where the connections confit might be stored so I can delete them, but no joy. Any tips or do I just delete JMRI and re-install?
     
    Scott Eric Catalano likes this.
  13. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Well I'm making slow progress. I have control over my front and rear coupler servos, and the reverser servo. I have directional leds, and the start of a firebox glow (dimming the LED didn't work as well as I'd hoped, probably due to the low onboard power available). I have finally had some control over the motor, until the WeMOS D1 mini motor shield froze, apparently it is something they like to do due to flakey firmware. I'll keep working on the Arduino sketch to sort out variable momentum and variable braking whilst I think of a more reliable and smaller solution.
     
    Scott Eric Catalano likes this.
  14. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I've ordered
    5 x #2130 DRV8833 Dual Motor Driver Carrier
    5 x #2115 Pololu 5V Step-Up Voltage Regulator U3V12F5
    5 x #2116 Pololu 9V Step-Up Voltage Regulator U3V12F9

    This should allow me to run off a (decent sized!) 1s Li-Po and safely provide power to the ESP8266, servos, LED and motor.

    Whilst I am waiting, I'm doing a code cleanup, starting afresh. At present I'm successfully sending the commands to the WeMos D1 Mini over Wi-Fi via PuTTy.

    My next challenge is parsing those commands more simply than previously. i was looking at String (as David B used) but I gather that is RAM intensive. I then looked at CmdMessage, but I can't find any examples which would enable me to get my head around it. So I'm now going to look at the string.h library, which uses strtok and strlen etc...except I've had enough for 1 day!!
     
    Scott Eric Catalano likes this.
  15. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon,

    You're pressing on then ?

    A few things that might help. First, don't get too hung up on lack of resources on the ESP. It's vastly superior to the 8 bit Atmel's found on Uno etc.

    Parsing those numbers ? Gregg gives you one solution in Base Station code.
    At around line 101 in PacketRegister.cpp

    Code:
    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)////<-This bit does the parsing///char *s being a 'C' string character array
        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("<T");
      INTERFACE.print(nReg); INTERFACE.print(" ");
      INTERFACE.print(tSpeed); INTERFACE.print(" ");
      INTERFACE.print(tDirection);
      INTERFACE.print(">");
     
      speedTable[nReg]=tDirection==1?tSpeed:-tSpeed;
    http://www.cplusplus.com/reference/cstdio/scanf/

    I've not tried sscanf() myself, having found my solution in strtok().

    Hope this helps.

    Steve.
     
    Scott Eric Catalano likes this.
  16. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    That's actually a big help Steve!
    I've kept looking through Greggs code, but hadn't looked closely at PacketRegister.cpp as I stupidly thought it was only relevant to generating the DCC packets.

    The sketch that I have had working on all counts (except proving the motor actually works) is actually the base station code with extras. I'm trying to build this up from scratch. Gives me an appreciation for what Gregg and others have done!
     
    Scott Eric Catalano likes this.
  17. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    :(
    Esp8266 doesn't currently support sscanf per Github but it is scheduled for release.

    What strikes me as strange is that sscanf and sprintf are in the DCC++ base station code, and it compiles fine for the WeMos D1 Mini. I even tried #include < stdio.h >
    Per the example sketch, to no avail.

    For now, strtok will be the workaround. I was trying to find a more elegant solution.

    And I've been checking out Robin_2 work on the Deltang boards, as I see that as a valid alternative, better packaging of wireless/Arduino/voltage reg/motor drive than the WeMos + motor shield and 2x voltage Regs. And probably less overhead if not using wifi. Still, I'll get this sorted first.
     
    Scott Eric Catalano likes this.
  18. Atani

    Atani TrainBoard Member

    1,466
    1,736
    37
    Scott Eric Catalano likes this.
  19. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    That's a fair chunk of code to work through! I'll likely take your lead and try to understand strtok. I'm heading away from home for 4 days so will have to park the experimenting for a bit. I got a fair way in last night but didn't know how to change my string 'data' from a const char to a char. So that's what I'll google next. That and some sort of indexing method for strtok.

    For what it is worth, here is my 'clean sheet of paper' re-write as it stands;

    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>
    #include <WEMOS_Motor.h>      //specific to the WeMos D1 Mini Motor shield, which has sketchy firmware and will hang if you look at it sideways
    
    //===================================================================================
    // WIFI COMMUNICATION
    #define IP_ADDRESS { 192, 168, 54, 103 }            //DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
    #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++ Config Variable
    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
    
    //===================================================================================
    void setup() {
      Serial.begin(19200);            // configure serial interface
      Serial.println("\nSerial Begin 19200 Baud");
      Serial.flush();
      // WIFI Begin
      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);
          delay(250);
          digitalWrite(LED_BUILTIN, HIGH);
          delay(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 process_data (const char * data)
    {
      Serial.println (data);     //for now just print it
    
      if (strchr(data, 't'))      //I would prefer this to be a switch
      {
        Serial.print("Throttle Command");
     
        char str[] = "t 1 5565 125 1";  //sample string, I need to make this point to 'data', but 'data' needs to be a char, not const char
        char * pch;
        pch = strtok (str, " ");
        while (pch != NULL)             //I need to work out how to index strtok to allocate values to the variables
        {
          sprintf("%s\n",pch);
          pch = strtok (NULL, " ");
        }
      }
      if (strchr(data, 'f'))
      {
        Serial.print("Function Command");
      }
    }// end of process_data
    
    void processIncomingByte (const byte inByte)
    {
      const unsigned int MAX_INPUT = 32;           //max length of a DCC++ command packet
      static char input_line [MAX_INPUT];
      static unsigned int input_pos = 0;
    
      switch (inByte)
      {
        case '<':                    // end of text
          input_pos = 0;                //reset buffer for next time
          break;
    
        case '>':                    // end of text
          input_line [input_pos] = 0; //terminating null byte
          process_data (input_line);  //terminator reached, process input line here
          break;
    
        case '\n':                    // discard new line
          break;
    
        case '\r':                      //discard carriage return
          break;
    
        default:
          if (input_pos < MAX_INPUT)  // keep adding if not full....allow for terminating null byte
            input_line [input_pos++] = inByte;
    
      } // end of switch
    } //end of processIncomingByte
    
    //===================================================================================
    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()
    
      //check client for data
      if (Client.connected())
      {
        if (Client.available())
        {
          //get data from the telnet client and push it to the UART
          while (Client.available())
            processIncomingByte (Client.read ());
        } // if client available
      } // if client connected
    } // SerialCommandProcess
    
    //===================================================================================
    
    void loop()
    {
      SerialCommandProcess();              // check for, and process, and new serial commands
      //Functions();                         // to do; action functions
      //MotorDriveProcess();                 // to do; action motor commands arising from serial command process
    }
    
     
    Last edited: Jan 11, 2017
    Scott Eric Catalano likes this.
  20. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Here's a modified version of how I implemented strtok() to parse the return strings eg. <T3 47 1>.

    This time to do the command strings. You could do away with processIncomingByte() as that's taken care of as strtok's delimiters.

    You might need to tweak it some as I've not tested.

    The major change is parsing the loco number, as in my version everything fitted into a byte, char, uint8_t whatever, there is no loco number returned !


    Code:
    void parsetCmd(String tCmd) {//tCmd is an Arduino String(), see below as might not be needed.
    
      char instring[18];//Eighteen is a minimum, I would add a few more. And see below if it's actually needed.
      tCmd.toCharArray(instring, 18);//If you build a 'C' string from the incoming char's you can get rid of this also.
      char delimiters[] = "<t >\r\n";
      char *valPosition;
      valPosition = strtok(instring, delimiters);
      int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
      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
    And hello Bradford from Wakey :)
     
    Scott Eric Catalano likes this.

Share This Page