3

I have a processing sketch which needs to set up 2 connections with USB devices. I cannot tell in advance which device is USB0 and which is USB1. (not that I am aware off atleast)

One of the devices awnsers with hello the other one does not answer at all. Therefor I have written code with a simple timeout. In the setup I check continously if there are bytes to read. But both a while and an if statement yield incorrect results

while( dccCentral.available() < 5 ) {
    if( dccCentral.available() >= 5) break;
    if(millis() > 5000 ) {
        println("timeout occured");
        println(dccCentral.available());
        break;
    }
}

These lines are in setup. The text "timeout occured" is always printed. Underneath it, the result of dccCentral.available() is printed. This number is 12 which is correct.

regardless, if dccCentral.available() prints 12 at that time. The first if-statement:

if( dccCentral.available() >= 5) break;

should already have break'ed out of the while loop before this time-out should occur. The while-loop itself should also quit itself when 5 or more bytes are received.

Why do both these lines

while( dccCentral.available() < 5 ) {
    if( dccCentral.available() >= 5) break;

fail?

bask185
  • 377
  • 1
  • 3
  • 12
  • I know that inside the while loop, the result of the .available() is 0 all the time. But why does it say 12 when the time-out occurs? Why is this happening? – bask185 Jul 05 '20 at 20:54

1 Answers1

2

Personally I try to avoid while loops unless there isn't another way (e.g. inside a thread) and that is avoid both logic pitfalls and messing with the lifecycle of other objects that might need a bit of time to initialise.

If you send strings from Arduino and also use println() you could init the port to easily catch that using Serial's bufferUntil() in conjuction with serialEvent() to finally readString().

Once you start getting data in, you could:

  1. use references to the serial ports you're after and a couple of extra ones until you know which port is which
  2. use a boolean "toggle" to only handle the "hello" once
  3. if the hello was received, you can use the serialEvent() Serial argument to assign dccCentral and by process of elimination assign the other port

Here's a commented sketch to illustrate the idea:

import processing.serial.*;

// be sure to set this to the baud rate your device use with Arduino as well
final int BAUD_RATE = 115200;
// reference to Serial port sending "Hello" (when that get's detected)
Serial dccCentral;
// reference to the other Serial port
Serial otherDevice;
// temporary references
Serial usb0;
Serial usb1;
// 'toggle' to keep track where the hello was received and handled or not (by default initialised as false)
boolean wasHelloReceived;

void setup(){
    usb0 = initSerial("/dev/ttyUSB0", BAUD_RATE);
    usb1 = initSerial("/dev/ttyUSB1", BAUD_RATE);
}

Serial initSerial(String portName, int baudRate){
    Serial port = null;

    try{
        port = new Serial(this, portName, baudRate);
        // if sending strings and using println() from Arduino
        // you can buffer all chars until the new line ('\n') character is found
        port.bufferUntil('\n');
    }catch(Exception e){
        println("error initialising port: " + portName);
        println("double check name, cable connections and close other software using the same port");
        e.printStackTrace();
    }

    return port;
}

void draw(){
    background(0);
    text("wasHelloReceived: " + wasHelloReceived + "\n"
        +"dccCentral: " + dccCentral + "\n" 
        +"otherDevice: " + otherDevice , 10 ,15);
    // do something with the devices once they're ready (e.g. send a message every 3 seconds)
    if(millis() % 3000 == 0){
        if(dccCentral != null){
            dccCentral.write("ping\n");
        }
        if(otherDevice != null){
            otherDevice.write("pong\n");
        }
    }
}

void serialEvent(Serial port){
    try{
        String serialString = port.readString();
        // if the received string is not null, nor empty
        if(serialString != null && !serialString.isEmpty()){
            // for debugging purposes display the data received
            println("received from serial: " + serialString);
            // trim any white space
            serialString = serialString.trim();
            // check if "hello" was received
            if(serialString.equals("hello")){
                println("hello detected!");
                // if the dccCEntral (hello sending) serial port wasn't assigned yet, assign it
                // think of this as debouncing a button: setting the port once "hello" was received should happen only once
                if(!wasHelloReceived){

                    // now what dccCentral is found, assign it to the named reference
                    dccCentral = port;

                    // by process elimiation, assign the other port 
                    // (e.g. if dccCentral == usb0, then other is usb1 and vice versa)
                    otherDevice = (dccCentral == usb0 ? usb1 : usb0);
                    /*
                    the above is the same as
                    if(dccCentral == usb0){
                        otherDevice = usb1;
                    }else{
                        otherDevice = usb0;
                    }
                    */

                    wasHelloReceived = true;
                }
            }
        }
    }catch(Exception e){
        println("error processing serial data");
        e.printStackTrace();
    }
}

Note the above code hasn't been tested so it may include syntax errors, but hopefully the point gets across.

I can't help notice that USB0/USB1 are how serial devices sometimes show up on Linux. If you're working with a Raspberry Pi I can recommend a slightly easier way if you're comfortable with Python. The PySerial has a few tricks up it's sleeve:

  1. You can simply call: python -m serial.tools.list_ports -v which will list ports with extra information such as serial number of the serial converter chipset. This could be useful to tell which device is which, regardless of the manufacturer and USB port used
  2. Other than the serial port name/location, it supports multiple ways (URLs) of accessing the port with a very clever: hwgrep:// will allow you to filter a device by it's unique serial number

Here's a basic list_ports -v output for two devices with the same chipset:

column 1

/dev/ttyUSB9        
    desc: TTL232R-3V3
    hwid: USB VID:PID=0403:6001 SER=FT94O21P LOCATION=1-2.2

column 2

/dev/ttyUSB8        
    desc: TTL232R-3V3
    hwid: USB VID:PID=0403:6001 SER=FT94MKCI LOCATION=1-2.1.4

To assign the devices using serial you would use something like:

"hwgrep://FT94O21P"
"hwgrep://FT94MKCI"

Update

It might help to step by step debug the system and try one port a time. The idea is to get the bit of code reading the expected serial string tight. Here's a basic example that should simply accumulate one char at a time into a string and display it:

import processing.serial.*;

Serial port;

String fromSerial = "";

void setup(){
  size(300,300);
  port = initSerial("/dev/ttyUSB0", 115200);
}

Serial initSerial(String portName, int baudRate){
    Serial port = null;

    try{
        port = new Serial(this, portName, baudRate);
        // if sending strings and using println() from Arduino
        // you can buffer all chars until the new line ('\n') character is found
        port.bufferUntil('\n');
    }catch(Exception e){
        println("error initialising port: " + portName);
        println("double check name, cable connections and close other software using the same port");
        e.printStackTrace();
    }

    return port;
}

void draw(){
  
  if(port != null){
    if(port.available() > 0){
      char inChar = port.readChar();
      fromSerial += inChar;
      if(inChar == '\n'){
        println("newline encountered");
        println(fromSerial.split("\n"));
      }
    }
  }
  
  background(0);
  text("from serial:" + fromSerial, 10,15);
}

If the data from dccCentral comes in a expected: great, the code can be simplfied and right conditions applied to filter the device in the future, otherwise it should help pin point communication issues getting the "hello" in the first place (which would be 6 bytes ("hello"(5) + '\n') if sent with Serial.println() from Arduino)

Regarding Python, no problem at all. Should the idea help in the future you can check out this answer. (AFAIK Processing Serial uses JSSC behind the scenes)

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • thnx. Normally I never use while loops at all nor do I use delays, not in processing not in C (except for the main loop). But in setup I'd still use them occasionally for these things. On another note, why do you recommend the "SerialEvent"? I tried to use it once, it gave me the most atomic buggy code I have seen. I found it to be more reliable to read in bytes in the begiining of void loop than to do it at a random undefined place. I also cannot/won't use python. I cannot simple recode my project and the device ID is useless when I swap out hardware components. – bask185 Jul 07 '20 at 06:29
  • @bask185 No worries. Thank you for the updated information: the more the better people will be able to support. I haven't ran into `serialEvent` issues in the past. if there is an issue parsing data in that event it will disable itself after the first failure in my experience. I've posted a minimal sketch above to test receiving characters from Arduino. Hopefully it will help double check that you receive everything correctly from Arduino. In theory, you might be able to get away if simply check which device sends data (e.g. `if(port.available() > 0))`) instead of waiting for the full hello ? – George Profenza Jul 07 '20 at 09:14