Delay with millis() not working (it varies randomly)

Hello there,

I've been dealing with a RFID reader:

http://forum.processing.org/two/discussion/12920/problem-reading-a-rfid-reader http://forum.processing.org/two/discussion/13077/how-to-load-save-data-from-serial-port-to-a-txt-file

What I want is that a card that hasn't been read previously is read, it displays an image for a certain amount of time. The thing is that this times varies randomly and I don't know why.

For example: now is set to 25 seconds (25000 milliseconds), I tried with one card and the image was displayed for 17 seconds, I tried another card and it was displayed for 22 seconds, and 19 seconds with a third card.

GoToLoop pointed (here) something about synchronized, and I've been reading about it but I still don't get it (in case this might be the solution).

Any ideas? Thanks!

This is the code:

import processing.serial.Serial;

String mensaje = "Texto inicial";

int tiempo; // this line and the next is for the delay time of the image
int espera = 25000;

PImage imagen;

int contador = 1;

Table table;

void setup() {
  size(900, 600);
  frame.setBackground(new java.awt.Color(0, 0, 0)); // fullscreen moder with black background
  imagen = loadImage("1.jpg");

  String[] portas = Serial.list(); // load serial port
  printArray(portas);

  new Serial(this, portas[0], 9600).bufferUntil(ENTER);

  tiempo = millis();

  table = loadTable("data.csv", "header"); // load the table with "id" and "state"
  table.setColumnType("state", Table.INT);


  println(table.getColumnTitles());
  println();
  println(table.getColumnTypes());
  println();
}

void draw () {  

  TableRow result = table.findRow(mensaje, "id"); //here i find the row with the ID i get from the serial port
  int state = result.getInt("state"); //here i get the value of the state of the id read by the serial

  if (state == 1) {  // if state is 1, wich means false, which means that is the first time the card is read, then show the image
    imageMode(CENTER);
    image(imagen, width/2, height/2); // show the image
    if (millis() - tiempo >= espera) { //wait 25 seconds
      tiempo = millis();
      contador++;
      result.setInt("state", 2); // here i change the value of state
      // saveTable(table, "data/data.csv"); // i save the table for any future run of the program - IS NOT WORKING
    }
  } else {   // if state is 2, it means that the card was used, so don't display anything
    background(0);
  }
}

void serialEvent(Serial s) {
  mensaje = s.readString().trim();
}

This is the data I'm loading. 1 is for "haven't been read", 2 is for "has been read"

Answers

  • edited November 2015
    • I think millis()'s irregularity comes from the fact Table entries are sharing it.
    • And we need to individualize for each entry to have its own tiempo.
    • I've come up w/ the idea to use IntDict to temporarily hold the active id and its display time.
    • Once millis() reaches an id's DELAY, a remove() is issued on it.

    // forum.processing.org/two/discussion/13137/
    // delay-with-millis-not-working-it-varies-randomly
    
    // GoToLoop (2015-Oct-21)
    
    import processing.serial.Serial;
    import java.util.Iterator;
    import java.io.ByteArrayInputStream;
    
    static final String CSV_FILE = "data.csv", CSV[] = {
      "id,state", 
      "4500B8C02A17,0", 
      "32007D958C56,0"
    };
    
    static final int FPS = 10, DELAY = 25 * 1000, BAUDS = 9600;
    
    final IntDict idTimers = new IntDict();
    Table table;
    
    void setup() {
      size(600, 600);
      smooth(4);
      frameRate(FPS);
    
      textAlign(LEFT, BOTTOM);
      textSize(030);
      fill(0200);
    
      getCSV();
      println(table.getColumnTitles());
      printArray(table.getStringColumn("id"));
      println();
    
      final String[] ports = Serial.list();
      printArray(ports);
      //new Serial(this, ports[0], BAUDS).bufferUntil(ENTER);
    
      idTimers.set("GoToLoop", millis() + DELAY);
      idTimers.set("ElunicoTomas", millis() + DELAY + 5000);
    }
    
    void draw() {
      final int ms = millis(), len = idTimers.size();
    
      background(0);
      frame.setTitle(frameCount + "  -  " + ms + "  -  " + len);
    
      if (len > 0) {
        final String[] ids;
        final int[] expires;
    
        synchronized (idTimers) {
          final Iterator<Integer> delays = idTimers.values().iterator();
          while (delays.hasNext())  if (ms >= delays.next())  delays.remove();
    
          ids = idTimers.keyArray();
          expires = idTimers.valueArray();
        }
    
        for (int i = 0; i != ids.length; )
          text(ids[i] + "  -  " + expires[i], width>>3, ++i * height/12);
      }
    }
    
    void serialEvent(Serial s) {
      final String id = s.readString().trim();
      final TableRow row = table.findRow(id, "id");
      final int state = row != null? row.getInt("state") : -1;
    
      if (state == 0)  synchronized (idTimers) {
        idTimers.set(id, millis() + DELAY);
      }
    
      if (state >= 0)  row.setInt("state", state + 1);
    }
    
    void getCSV() {
      table = loadTable(CSV_FILE, "header");
    
      if (table == null) {
        final byte[] csv = join(CSV, ENTER).getBytes();
        final InputStream is = new ByteArrayInputStream(csv);
    
        try {
          table = new Table(is, "header, csv");
        }
    
        catch (IOException ex) {
          throw new RuntimeException(ex);
        }
      }
    
      table.setColumnType("state", Table.INT);
      table.setMissingInt(-1);
    }
    
  • Hi GoToLoop, I've been really busy @ university and haven't had time to give attention to your answer.

    My proble. Is that I don't know how to implement your suggestion into my code. I see that your timers work perfectly but I don't know how to apply this into my sketch. Any tips?

    Thanks!

  • edited November 2015

    In order to adapt my example to yours you need to comprehend what I did.
    You need to be more specific about which part(s) you don't get.

    Main difference is that your sketch got 1 String mensaje & 1 int tiempo.
    And in case another matching readString() arrives in serialEvent() mensaje is overridden, even though the tiempo for current 1 hasn't finished yet!

    In order to keep track for each readString() mine relies on an IntDict container called idTimers:
    https://Processing.org/reference/IntDict.html

    Therefore each approved id is paired up w/ an int value representing how much time it is displayed till it's remove().

  • And in case another matching readString() arrives in serialEvent() mensaje is overridden, even though the tiempo for current 1 hasn't finished yet!

    The first time I read a card the time is displayed is worng, how could this be if mensaje hasn't been override yet? (though I get your point here)

    import processing.serial.Serial;
    import java.util.Iterator; // I DON´T KNOW WHAT IS THIS LIBRARY FOR
    import java.io.ByteArrayInputStream; // I DON´T KNOW WHAT IS THIS LIBRARY FOR
    
    static final String CSV_FILE = "data.csv", CSV[] = { // why do you create this?
      "id,state", 
      "4500B8C02A17,0", 
      "32007D958C56,0"
    };
    
    static final int FPS = 10, DELAY = 25 * 1000, BAUDS = 9600;
    
    final IntDict idTimers = new IntDict(); // i've read the reference and i think i get what this does
    Table table;
    
    void setup() {
      size(600, 600);
      smooth(4);
      frameRate(FPS);
    
      textAlign(LEFT, BOTTOM);
      textSize(030);
      fill(0200);
    
      getCSV();                                  // OK
      println(table.getColumnTitles());
      printArray(table.getStringColumn("id"));
      println();
    
     // final String[] ports = Serial.list();
      //printArray(ports);
     // new Serial(this, ports[0], BAUDS).bufferUntil(ENTER);
    
      idTimers.set("GoToLoop", millis() + DELAY);
      idTimers.set("ElunicoTomas", millis() + DELAY + 5000);
    }
    
    void draw() {
      final int ms = millis(), len = idTimers.size(), expires[]; // i'm already lost here
      final String[] ids;
    
      background(0);
      frame.setTitle(frameCount + "  -  " + ms + "  -  " + len);
    
      if (len > 0) {
        synchronized (idTimers) {
          final Iterator<Integer> delays = idTimers.values().iterator(); // what is an iterator for?
          while (delays.hasNext ())  if (ms >= delays.next())  delays.remove();
    
          ids = idTimers.keyArray();
          expires = idTimers.valueArray();
        }
    
        for (int i = 0; i != ids.length; )
          text(ids[i] + "  -  " + expires[i], width>>3, ++i * height/12);
      }
    }
    
    void serialEvent(Serial s) {
      final String id = s.readString().trim();
      final TableRow row = table.findRow(id, "id");
      final int state = row != null? row.getInt("state") : -1;
    
      if (state == 0)  synchronized (idTimers) {
        idTimers.set(id, millis() + DELAY);
      }
    
      if (state >= 0)  row.setInt("state", state + 1);
    }
    
    void getCSV() {
      table = loadTable(CSV_FILE, "header");
    
      if (table == null) {
        final byte[] csv = join(CSV, ENTER).getBytes();
        final InputStream is = new ByteArrayInputStream(csv);
    
        try {
          table = new Table(is, "header, csv");
        }
    
        catch (IOException ex) {
          throw new RuntimeException(ex);
        }
      }
    
      table.setColumnType("state", Table.INT);
      table.setMissingInt(-1);
    }
    
  • I'll try your code tomorrow with the serial reader as i dont have it here. thanks

  • edited November 2015
    import java.io.ByteArrayInputStream; // I DON´T KNOW WHAT IS THIS LIBRARY FOR
    static final String CSV_FILE = "data.csv", CSV[] = { // why do you create this?
    

    I haven't created my own "data.csv". Instead I've made a mini sample String[] array called CSV[].
    Unfortunately loadTable() doesn't work w/ arrays. So I had to turn CSV[] into some ByteArrayInputStream in order to instantiate a Table w/ that.
    In other words, you can get rid of both since you've already got your own ".csv" file. :ar!

    final IntDict idTimers = new IntDict();
    // i've read the reference and i think i get what this does
    

    It's roughly the same as HashMap<String, Integer>. But its keys are ordered like a List. And its values use int rather than Integer.

    import java.util.Iterator; // I DON´T KNOW WHAT IS THIS LIBRARY FOR
    final Iterator<Integer> delays = idTimers.values().iterator();
    // what is an iterator for?
    

    An Iterator is like the old Enumeration:
    http://docs.Oracle.com/javase/8/docs/api/java/util/Enumeration.html

    It's what an enhanced for ( : ) loop relies on behind the scenes.

    The reason why I couldn't use for ( : ) and had to depend on an Iterator + while () loop is b/c I'm using remove():
    http://docs.Oracle.com/javase/8/docs/api/java/util/Iterator.html#remove--

    And we can't use remove() inside enhanced for ( : ) loops.
    And neither we can traverse an IntDict w/ some regular for ( ;; ) b/c its keys are String and not numerical. :(

    final int ms = millis(), len = idTimers.size(), expires[]; // i'm already lost here
    final String[] ids;
    

    I'm caching current millis() & IntDict's size() as local variables till the end of draw().
    Local variables ids[] & expires[] are just to store respectively IntDict's keyArray() & valueArray().
    So they can be displayed later @ here:

    for (int i = 0; i != ids.length; )
          text(ids[i] + "  -  " + expires[i], width>>3, ++i * height/12);
    
  • edited November 2015

    Again, thanks for the detailed explanation. But I lack the knowledge to fully understand your code, I mean, I get your point but there are several "java codes" that I still don't get (and I'm in a rush now :( ), so I'll try to figure a way to display images without using millis, i.e. you swipe one card and one image is displayed until you swipe another card. Or something similar.

    Thanks!

Sign In or Register to comment.