DCC++ Wi-Fi Receiver Trackside or Onboard

Simon Mitchell Oct 30, 2016

  1. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Simon

    If I can do this with the simple chat server example I posed about previously here....

    Multi User Chat Application http://cs.lmu.edu/~ray/notes/javanetexamples/

    And I only tinkered with a few lines of the log on response messaging, in around 20 minutes I got JMRI connected and happy, with as many PuTTY's as you like all listening and receiving:)

    click image for full size.

    CaptureTTY.PNG
     
    Last edited: Feb 14, 2017
    Scott Eric Catalano likes this.
  2. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Roughly tweaked example.....
    Code:
    //Example 26 (updated)
    
    import java.io.DataInputStream;
    import java.io.PrintStream;
    import java.io.IOException;
    import java.net.Socket;
    import java.net.ServerSocket;
    
    /*
     * A chat server that delivers public and private messages.
     */
    public class MultiThreadChatServerSync {
    
      // The server socket.
      private static ServerSocket serverSocket = null;
      // The client socket.
      private static Socket clientSocket = null;
    
      // This chat server can accept up to maxClientsCount clients' connections.
      private static final int maxClientsCount = 10;
      private static final clientThread[] threads = new clientThread[maxClientsCount];
    
      public static void main(String args[]) {
    
        // The default port number.
        int portNumber = 4445;
        if (args.length < 1) {
          System.out.println("Usage: java MultiThreadChatServerSync <portNumber>\n"
              + "Now using port number=" + portNumber);
        } else {
          portNumber = Integer.valueOf(args[0]).intValue();
        }
    
        /*
         * Open a server socket on the portNumber (default 2222). Note that we can
         * not choose a port less than 1023 if we are not privileged users (root).
         */
        try {
          serverSocket = new ServerSocket(portNumber);
        } catch (IOException e) {
          System.out.println(e);
        }
    
        /*
         * Create a client socket for each connection and pass it to a new client
         * thread.
         */
        while (true) {
          try {
            clientSocket = serverSocket.accept();
            int i = 0;
            for (i = 0; i < maxClientsCount; i++) {
              if (threads[i] == null) {
                (threads[i] = new clientThread(clientSocket, threads)).start();
                break;
              }
            }
            if (i == maxClientsCount) {
              PrintStream os = new PrintStream(clientSocket.getOutputStream());
              os.println("Server too busy. Try later.");
              os.close();
              clientSocket.close();
            }
          } catch (IOException e) {
            System.out.println(e);
          }
        }
      }
    }
    
    /*
     * The chat client thread. This client thread opens the input and the output
     * streams for a particular client, ask the client's name, informs all the
     * clients connected to the server about the fact that a new client has joined
     * the chat room, and as long as it receive data, echos that data back to all
     * other clients. The thread broadcast the incoming messages to all clients and
     * routes the private message to the particular client. When a client leaves the
     * chat room this thread informs also all the clients about that and terminates.
     */
    class clientThread extends Thread {
    
      private String clientName = null;
      private DataInputStream is = null;
      private PrintStream os = null;
      private Socket clientSocket = null;
      private final clientThread[] threads;
      private int maxClientsCount;
    
      public clientThread(Socket clientSocket, clientThread[] threads) {
        this.clientSocket = clientSocket;
        this.threads = threads;
        maxClientsCount = threads.length;
      }
    
      public void run() {
        int maxClientsCount = this.maxClientsCount;
        clientThread[] threads = this.threads;
    
        try {
          /*
           * Create input and output streams for this client.
           */
          is = new DataInputStream(clientSocket.getInputStream());
          os = new PrintStream(clientSocket.getOutputStream());
          String name;
          while (true) {     
            os.println("");
            name = is.readLine().trim();
            if (name.indexOf('@') == -1) {
              break;
            } else {
                os.println("");
              //os.println("The name should not contain '@' character.");
            }
          }
    
          /* Welcome the new the client. */
          os.println("<p0><iDCC++ BASE STATION FOR ARDUINO UNO / ARDUINO MOTOR SHIELD: V-1.2.1+ / Feb 14 2017 11:49:47>");
          //os.println("Welcome " + name
              //+ " to our chat room.\nTo leave enter /quit in a new line.");
          synchronized (this) {
            for (int i = 0; i < maxClientsCount; i++) {
              if (threads[i] != null && threads[i] == this) {
                clientName = "@" + name;
                break;
              }
            }
            for (int i = 1; i < maxClientsCount; i++) {//Now starts at 1 so shouldn't go to JMRI
              if (threads[i] != null && threads[i] != this) {
                threads[i].os.println("* A New Decoder " + name + " has logged on ! *");
              }
            }
          }
          /* Start the conversation. */
          while (true) {
            String line = is.readLine();
            if (line.startsWith("/quit")) {
              break;
            }
            /* If the message is private sent it to the given client. */
            if (line.startsWith("@")) {
              String[] words = line.split("\\s", 2);
              if (words.length > 1 && words[1] != null) {
                words[1] = words[1].trim();
                if (!words[1].isEmpty()) {
                  synchronized (this) {
                    for (int i = 0; i < maxClientsCount; i++) {
                      if (threads[i] != null && threads[i] != this
                          && threads[i].clientName != null
                          && threads[i].clientName.equals(words[0])) {
                        threads[i].os.println("<" + name + "> " + words[1]);
                        /*
                         * Echo this message to let the client know the private
                         * message was sent.
                         */
                        this.os.println(">" + name + "> " + words[1]);
                        break;
                      }
                    }
                  }
                }
              }
            } else {
              /* The message is public, broadcast it to all other clients. */
              synchronized (this) {
                for (int i = 0; i < maxClientsCount; i++) {
                  if (threads[i] != null && threads[i].clientName != null) {
                    threads[i].os.println("<" + name + "> " + line);
                  }
                }
              }
            }
          }
          synchronized (this) {
            for (int i = 0; i < maxClientsCount; i++) {
              if (threads[i] != null && threads[i] != this
                  && threads[i].clientName != null) {
                threads[i].os.println("*** The user " + name
                    + " is leaving the chat room !!! ***");
              }
            }
          }
          os.println("*** Bye " + name + " ***");
    
          /*
           * Clean up. Set the current thread variable to null so that a new client
           * could be accepted by the server.
           */
          synchronized (this) {
            for (int i = 0; i < maxClientsCount; i++) {
              if (threads[i] == this) {
                threads[i] = null;
              }
            }
          }
          /*
           * Close the output stream, close the input stream, close the socket.
           */
          is.close();
          os.close();
          clientSocket.close();
        } catch (IOException e) {
        }
      }
    }
    Gives this type of output

    CapturePuTTY.PNG
     
    Last edited: Feb 14, 2017
    Scott Eric Catalano likes this.
  3. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    I've been thinking about the "turn JMRI on its head" thing, and i'm afraid that's just what it would do. The whole JMRI device connection setup is kind of built on the assumption of one "Connection" == one "Thing on the other side of the link I'm talking to".

    I like the scheme of using the chat server to handle the one-to-many aspect of the "device" side of JMRI. I suppose it would be possible to add a simple chat server to JMRI, and then pipe the "Connection" to that server, but changing the JMRI Connection to talk to multiple devices is probably going to break something.

    Let me think on that. Meanwhile I'm going to push the DCCppOverTCP changes (no more "SEND" and "RECEIVE" on the packets) tonight, for y'all to test out.
     
    Scott Eric Catalano likes this.
  4. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Mark

    Really no need, I can't see this being anything more than a niche interest anyways (I could be wrong). The chat server will meet the needs of this project nicely. The above example has everything covered and more, with only slight modification needed to suit my operation outline a few posts back. Despite me not wanting to write the darn thing, I have already got something that is almost ready to go.
    That 'scary' Java stuff is quite easy once you give it a few hours attention:)

    The link in post #141 is wrong sorry. Should be http://makemobiapps.blogspot.co.uk/p/multiple-client-server-chat-programming.html
     
    Last edited: Feb 14, 2017
    Scott Eric Catalano likes this.
  5. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Gents. I agree that this may be a niche interest. On the other hand, it may be bringing IoT things to model railways....

    My game plan for today is to play more with the DCC++ Ethernet -> ESP8266 1:1 connection and get the status command working. Steve proved it, so I must be doing something wrong, and I need to find out what that is for my own sake.

    I've taken a further look at how to get java to run on the RPi3 or the laptop. It's not so much the coding, that's one thing. It's also that the RPi3 with its GUI/cmd line interface is quite daunting. Same with the laptop. Uploading a sketch to an Arduino is child's play and now very familiar! Still that's what google is for I guess. I'm very grateful for the help Gents, please don't think I'm ignoring your help or guidance, I've just got a wall or two to jump first.

    With the chat server installed I can then strip out all the wifi server code out of the ESP8266 sketch and change it to being a WiFi client.
     
    Scott Eric Catalano likes this.
  6. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    JMRI is java, and that runs OK on RPi3, no?
     
    Scott Eric Catalano likes this.
  7. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    JMRI sure does run OK on RPi3...I'm the problem
     
    Scott Eric Catalano likes this.
  8. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    To test this demo, compile and run in Eclipse or your favourite Java environment.

    Special thanks to the original author please see link in #144 above.

    Once the server is running (BTW there is no GUI) in conjunction with the Console log you can start adding PuTTY instances @ localhost if you're on the same machine to the port you specified.

    The sever expects a log in name, start instance 1 of PuTTY by sending <s> emulating the JMRI connection.
    Additional instances would be the decoders, I called them <D1> <D2> etc

    As you log in a message will come back. The logic will then route messages according to origin sending <t 1 2820 45 1> from the 'JMRI' <s> pane
    should get routed to the decoder clients. Send a <T 1 45 1> back from any of the 'decoder' panes and it should only appear in the 'JMRI' pane.
    I've not coded for any other message types from decoders.

    Once you've got the idea, you can do a proper set up with JMRI pointed to the server as the 1st instance.

    I've no intention of doing more work on this (it'll need some:)) have fun.

    Code:
    //Example 26 (updated)
    
    import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.PrintStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    import java.net.ServerSocket;
    
    /*
     * A chat server that delivers public and private messages.
     */
    public class MultiThreadChatServerSync {
    
      // The server socket.
      private static ServerSocket serverSocket = null;
      // The client socket.
      private static Socket clientSocket = null;
    
      // This chat server can accept up to maxClientsCount clients' connections.
     
      private static final int maxClientsCount = 10;
      private static final clientThread[] threads = new clientThread[maxClientsCount];
    
      public static void main(String args[]) {
    
        // The default port number.
        int portNumber = 4445;//Set your port number here.
        if (args.length < 1) {
          System.out.println("Usage: java MultiThreadChatServerSync <portNumber>\n"
              + "Now using port number=" + portNumber);
        } else {
          portNumber = Integer.valueOf(args[0]).intValue();
        }
    
        /*
         * Open a server socket on the portNumber (default 4445 ). Note that we can
         * not choose a port less than 1023 if we are not privileged users (root).
         */
        try {
          serverSocket = new ServerSocket(portNumber);
        } catch (IOException e) {
          System.out.println(e);
        }
    
        /*
         * Create a client socket for each connection and pass it to a new client
         * thread.
         */
        while (true) {
          try {
            clientSocket = serverSocket.accept();
            int i = 0;
            for (i = 0; i < maxClientsCount; i++) {
              if (threads[i] == null) {
                (threads[i] = new clientThread(clientSocket, threads)).start();
                break;
              }
            }
            if (i == maxClientsCount) {
              PrintStream os = new PrintStream(clientSocket.getOutputStream());
              os.println("Server too busy. Try later.");
              os.close();
              clientSocket.close();
            }
          } catch (IOException e) {
            System.out.println(e);
          }
        }
      }
    }
    
    /*
     * The chat client thread. This client thread opens the input and the output
     * streams for a particular client, ask the client's name, informs all the
     * clients connected to the server about the fact that a new client has joined
     * the chat room, and as long as it receive data, echos that data back to all
     * other clients. The thread broadcast the incoming messages to all clients and
     * routes the private message to the particular client. When a client leaves the
     * chat room this thread informs also all the clients about that and terminates.
     */
    class clientThread extends Thread {
    
      private String clientName = null;
      private DataInputStream is = null;
      private PrintStream os = null;
      private Socket clientSocket = null;
      private final clientThread[] threads;
      private int maxClientsCount;
     
    
      public clientThread(Socket clientSocket, clientThread[] threads) {
        this.clientSocket = clientSocket;
        this.threads = threads;
        maxClientsCount = threads.length;
      }
     
    
      public void run() {
        int maxClientsCount = this.maxClientsCount;
        clientThread[] threads = this.threads;   
        try {
          /*
           * Create input and output streams for this client. 
           */
          is = new DataInputStream(clientSocket.getInputStream());
          os = new PrintStream(clientSocket.getOutputStream());
          BufferedReader lines = new BufferedReader(new InputStreamReader(is, "UTF-8"));
          String name;
          while (true) {
            name = lines.readLine();
            if (name.indexOf('@') == -1) {
              break;
            } else {
              os.println("The name should not contain '@' character.");
            }
          }
    
          /* Welcome the new the client. */
          os.println("<iDCC++ BASE STATION RELAY SERVER>"); //Demo welcome message not needed in production   
    
              //To leave enter /quit in a new line.");
          if(threads[1] == null){
              System.out.println("JMRI LOGGED IN");
              threads[0].os.println("<p1><iDCC++ BASE STATION RELAY SERVER>");         
          }
          synchronized (this) {
            for (int i = 0; i < maxClientsCount; i++) {
              if (threads[i] != null && threads[i] == this) {
                clientName = "@" + name;
                break;
              }
            }       
              
            for (int i = 1; i < maxClientsCount; i++) {
              if (threads[i] != null && threads[i] != this) {
                threads[i].os.println("Decoder " + name + " has logged on.");
                
              }
            }                               
          }//The whole message handling is done in the next 20 lines. Nothing to it :)
          /* Start the conversation. */
          while (true) {
            String line = lines.readLine();     
            if (line.startsWith("/quit")) {
              break;
            }
            if(line.startsWith("<T")){//Any other replies will need catching in a similar manner else they go print to all!
                threads[0].os.println(line);//threads[0] should be the JMRI socket THE FIRST ONE CONNECTED ALWAYS!!!
    
            } else {
              /* The message is public JMRI ==>, broadcast it to all other clients. */
              synchronized (this) {
                System.out.println(line);
                for (int i = 1; i < maxClientsCount; i++) {
                  if (threads[i] != null && threads[i].clientName != null) {
                    threads[i].os.println(line);//threads[i] will start at 1 so no echo will go back to JMRI 
                    
                  }
                }
              }
            }
          }
          /*
           * Clean up. Set the current thread variable to null so that a new client
           * could be accepted by the server.
           */
          synchronized (this) {
            for (int i = 0; i < maxClientsCount; i++) {
              if (threads[i] == this) {
                threads[i] = null;
              }
            }
          }
          /*
           * Close the output stream, close the input stream, close the socket.
           */
          is.close();
          os.close();
          clientSocket.close();
        } catch (IOException e) {
        }
      }
    }
     
    Scott Eric Catalano likes this.
  9. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Steve, appreciate you burning the midnight oil on someone elses project!
     
    Scott Eric Catalano likes this.
  10. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I've done some more playing with the 'DCC++ Ethernet' and 'DCC++ Serial' connections. I tried to use PuTTY to send the above suggested string to JMRI and the connection was refused. So I went hunting for the JMRI source to identify what the required syntax was. I'm pretty sure I've found a bug to squash, and have reported it. I think the handshake from DCC++ V1.2.1+ varied in syntax.

    This is not Regonised;
    <p0><iDCC++ BASE STATION FOR ARDUINO UNO / ARDUINO MOTOR SHIELD: V-1.2.1+ / Feb 15 2017 16:43:06><N1: 192.168.54.41><X><X>
    This is;
    <p0><iDCC++BASE STATION FOR ARDUINO UNO / ARDUINO MOTOR SHIELD: BUILD V-1.1 / Feb 15 2017 16:43:06><N1: 192.168.54.41><X><X>

    Great. Except it keeps sending the <s> command, and won't let me turn on track power or drive locomotives. FFS. there must be some other handshake element which happens after the <s> but is not in that section of the base station code. Which may well have changed. I want to point this connection method at the chat server, hence I don't see this as a band aid approach. I need to get JMRI talking to the decoders somehow.

    I will next try to replicate what Steve kindly did overnight. As an aside, the screenshots show "SEND" and "RECEIVE". I think that means you were using "DCC++OverTCP" server? I can't get anything useful out of that at present, other than a raw repeat of the traffic monitor. Maybe that's because I don't have a working base station connected. I will try to use a simulator later on, and have changed my DCCpp_UNO.INO code as a temporary patch.
     
    Last edited: Feb 15, 2017
    Scott Eric Catalano likes this.
  11. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Nope

    No other servers were used in my demo set up, and that's the whole point, it's meant to simplify things.
    Get Eclipse, get on youtube to see how to you start a project, compile and run. Use putty as described 'till you know how it works then migrate to JMRI.
    Write a client test sketch for an ESP (DCC++ TCP blinky) and off you go, simples.

    The SEND RECEIVE were the direct output from my (latest) JMRI. I've not used Marks new code to eliminate them, that's your domain:)

    Study just for half an hour, you should have it working.

    Edit: On handshake, just sending power status and a shortened <iDCC++...... seems to be enough to get JMRI to accept it has connection. The rest would be optional or junk data anyways. I could open a throttle and start sending live commands no problem.
    And remember you can't hook up a real Base Station in to this network unless it was configured somehow as a client connection i.e IT would have to connect TO the server.
     
    Last edited: Feb 15, 2017
    Scott Eric Catalano likes this.
  12. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    I've been thinking a bit about this "integrate the chat server into JMRI" idea... I think there's a way I could make it work...

    There's different types of interfaces you can choose... "Serial/USB", "Network", "Simulator", "DCCppOverTCP" ...

    I think I could create a new Interface, call it "Broadcast" or "ChatServer" or something like that... it would implement the chat server, and would plug in to the rest of JMRI as though it was a single-point interface.

    I will examine the Chat Server code and see how feasible it is to integrate. If it's not a large task, I may try to tackle it some time this year.

    One of the key questions is how much info does the user need to provide in order to configure the server (IP addresses, port assignments, etc.).
     
  13. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Mark

    Just the same as for the other servers, IP address and port #

    There's still stuff in the code above that could be eliminated, and other stuff that needs adding or made more robust. I guess for the purpose of integration, that's not to important right now. Although a proper termination routine ought to be included, as once I ran as a compiled .jar I could only terminate via Task Manager. Better, would be a simple UI.
     
    Scott Eric Catalano likes this.
  14. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    Cool. Termination shouldn't be a problem. Since the integrated chat server would be running as a sub-process/thread of JMRI, worst case it would get killed when JMRI is terminated. It would be almost trivial to create a small window with an on/off button and maybe some status info as well.

    ETA: when in "off" mode, the chat server could shut down its "chat-side" connections and start behaving like the Simulator to keep the JMRI core from choking on a "lost connection" ...
     
    Scott Eric Catalano likes this.
  15. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    Hey... umm.. is the throttle stuff working correctly if you have a DCCppOverTCP connection between a client and server JMRI instance and you try to use a throttle on the client to drive a loco connected to a base station on the server?

    I think I just found a bug where all you get is full speed or full stop on throttle speed/direction changes.
     
    Scott Eric Catalano likes this.
  16. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    I've pushed the changes/fixes for DCCppOverTCP to my Github...

    https://github.com/msunderwd/JMRI/tree/DCCppOverTCP

    This should get rid of the "SEND" and "RECEIVE" prefixes, IFF both host and client instances of JMRI are running the "new" code. If either is running the "Old" code, it will revert to including the prefixes.

    It also fixes the bug I just mentioned in the mis-parsing of the data.

    Note that the Traffic Monitor still doesn't quite display everything that is going on... doesn't mean it isn't happening... so if you don't see something in the monitor, check to see if the actual effect really happened...
     
    Scott Eric Catalano likes this.
  17. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Mark, yes, I did see that behavior of full speed or nothing. I could only get some action by sending a DCC packet, and then only throttle and power on/off worked. Ive got a problem of some sort with JMRI (unknown exception..), I'll hook out the logs and send off for the team to look at. I think that's why I can't get throttles working, and I'm unsure if that's why the status command keeps sending. It doesn't do this for the simulator or the now modified uno on serial. Mind you, it doesn't do anything properly even with the Ethernet connection disabled...

    I've downloaded java and eclipse neon, had a go at the hello world. Can see how to import stuff from GIT in the JMRI instructions. I'm off to the boat today and Saturday/Sunday so may not get much more done for a few days.
     
    Scott Eric Catalano likes this.
  18. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    In a few days (maybe as early as Monday), JMRI will be releasing development version 4.7.1. It will include (among other things) my changes to the DCCppOverTCP code. So if you don't want to fool with building it yourself, a very little patience will get you a release to play with.
     
  19. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Mark, I haven't worked out how to build a dev version of JMRI yet, and probably won't before Monday. Sorry to fail and testing.

    I did get a chance last night to get the chat server example 26 above to run in eclipse. Had 2 instances of PuTTY playing with it. I might be able to play again later tonight. I'm hoping I can set up 2 clients, so that JMRI comes is on say port 2560, and the decoders are on a different port. This would be more robust than trying to get JMRI to be the first client to connect to the server.
     
    Scott Eric Catalano and TwinDad like this.
  20. TwinDad

    TwinDad TrainBoard Member

    1,844
    551
    34
    No worries. There's still some (thankfully unrelated to me) bugs to be worked out in the release, but I'll post up when it's ready. Or you can monitor JMRI.org where they will also post its availability.
     
    Scott Eric Catalano likes this.

Share This Page