Announcement

Collapse
No announcement yet.

Widget state pipelining

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #16
    Hi Gerrit and Mark,

    I've added some basic support for command queueing and deduplication on the client side in my photon library. Shouldn't be too hard to make it compile for a Teensy (I do use Photon string classes because well... it's a photon library) but switching to serial environment might still be too big a hurdle for you.

    I'm using this feature in my vehicle instrument cluster now. It works great :-). Command names and associated Actions go into a queue: If a Command name is re-reported before it gets its turn, the Action on the Command is replaced and the Command does not lose its place in line to avoid starvation of any one gauge.

    Actions are std::function<void()>, so you can legit do anything you want once the previously submitted commands are complete. Memory cost is proportional to the number of distinct commands that are possible, and whatever your function costs. Should be tens of bytes per suspended command generally I suppose.

    The library has always given the user control over when to block for a screen response, but hasn't provided a convenient means of making interactions with the screen completely decoupled from your main loop's execution. Hopefully this basic support with defer() and advance() helps!

    Comment


    • #17
      Originally posted by ESPsupport View Post
      There have been thoughts about the idea of non blocking writes, but it is fraught with difficulty, and, on a low end Arduino you will run out of memory very quickly.
      You also try a transmit queue, before adding something to the queue, you check the queue to see if the 'message' already exists with a different value, if so just update the value and don't add the new 'message'. The 'old' library already does this on received messages and it works well.
      I understand that coming up with a generic solution would be difficult but it's good to know that the approach is feasible.

      Hi Gerrit and Mark,

      I've added some basic support for command queueing and deduplication on the client side in my photon library. Shouldn't be too hard to make it compile for a Teensy (I do use Photon string classes because well... it's a photon library) but switching to serial environment might still be too big a hurdle for you.

      I'm using this feature in my vehicle instrument cluster now. It works great :-). Command names and associated Actions go into a queue: If a Command name is re-reported before it gets its turn, the Action on the Command is replaced and the Command does not lose its place in line to avoid starvation of any one gauge.

      Actions are std::function<void()>, so you can legit do anything you want once the previously submitted commands are complete. Memory cost is proportional to the number of distinct commands that are possible, and whatever your function costs. Should be tens of bytes per suspended command generally I suppose.

      The library has always given the user control over when to block for a screen response, but hasn't provided a convenient means of making interactions with the screen completely decoupled from your main loop's execution. Hopefully this basic support with defer() and advance() helps!
      Switching to the serial environment would involve a lot of work and for my use case it is not necessary that the display updates super fast. In my case it is far more important that the communication with the display is not blocking. I guess it would be best if I write my own message queue using the VisiGenie commands. This would also make it possible to assign different priorities to different messages, in my case giving priority to the bar/beat counter.

      O.T. I noticed the rev counter in your very nice dash redlines at 9000rpm, is it perhaps a rotary engine?

      Kind regards,

      Gerrit

      Comment


      • #18
        Gerrit It's actually a Toyota 4age 20v blacktop. Factory redline is 8400 but with a little effort you can spin them up around 9-9500.

        I've played with the visi genie serial API. Really no reason why they can't fit with the rest of the library and just use the defer/advance pattern. I'll look into adding the api family, even if you can't use it on account of your microcontroller :-)

        Comment


        • #19
          Originally posted by warriorofwire View Post
          Gerrit It's actually a Toyota 4age 20v blacktop. Factory redline is 8400 but with a little effort you can spin them up around 9-9500.

          I've played with the visi genie serial API. Really no reason why they can't fit with the rest of the library and just use the defer/advance pattern. I'll look into adding the api family, even if you can't use it on account of your microcontroller :-)
          At least I guessed the country right

          I've got some ideas on how to tackle this which I want to try. The thing is that I don't need a data queue because all the required data is already available in variables, all I need to do is keep track of which object is next in line to be updated.

          Kind regards,

          Gerrit

          Comment


          • #20
            I managed to get it working.

            The communication is now asynchronous. To achieve this and just copied the parts I needed from the library and adapted them slightly.

            Code:
            int           genieStateL           = GENIE_LINK_IDLE;      // state is idle on startup
            int           genieStateR           = GENIE_LINK_IDLE;      // state is idle on startup
            
            elapsedMicros sinceDisplayUpdate;                           // timer for updating the display queue
            unsigned int  displayUpdateInterval = 1100;                 // display queue update interval in microseconds
            
            int           objectQueueL[27];                             // left screen objects  0, string 0-15, knob 0-7, userImage0,string16
            int           objectQueueR[36];                             // right screen objects
            int           queueFirstIndexL      = 0;                    // object array index of first in queue, 0 if empty
            int           queueLastNoL          = 1;                    // number of last in queue
            int           queueFirstIndexR      = 0;
            int           queueLastNoR          = 1;
            The objectQueue arrays represent the objects on the screens, they contain the order in the queue for the particular object. The queueFirstIndex variable points to the first object in the queue, the queueLastNo variable contains highest number in the queue.

            A function is called to add an object to the queue:
            Code:
            void addToQueue(int objectIndex, int displayQueue){
              // left dislay is 0
              if (displayQueue==0){
                // check if object is in queue
                if (objectQueueL[objectIndex]==0){
                  objectQueueL[objectIndex]=queueLastNoL;
                  if (queueFirstIndexL==0) queueFirstIndexL=objectIndex;
                  queueLastNoL++;
                }
              } 
              // right display is 1
              else if (displayQueue==1){
                // check if object is in queue
                if (objectQueueR[objectIndex]==0){
                  if (queueFirstIndexR==0) queueFirstIndexR=objectIndex;
                  objectQueueR[objectIndex]=queueLastNoR;
                  queueLastNoR++;
                }
              }
            }
            The main loop checks for ACKs and calls the updateDisplay() function:
            Code:
            void loop(){
              int incomingByte;
              // first read midi
              usbMIDI.read();
              // check display serial ports for ACK or NACK and set state accordingly
              if (Serial2.available() > 0) {
                incomingByte = Serial2.read();
                switch(incomingByte){
                  case GENIE_ACK :
                    genieStateL=GENIE_LINK_IDLE;
             //       Serial.print("Left: ACK ");
                    break;
                  case GENIE_NAK :   
                    genieStateL=ERROR_NAK;
                    Serial.print("Left: NACK ");
                    break;    
                  default: 
                    Serial.print("LEFT received: ");
                    Serial.println(incomingByte, HEX);
                    break;
                }
              }
              if (Serial3.available() > 0) {
                incomingByte = Serial3.read();
                switch(incomingByte){
                  case GENIE_ACK : 
                    genieStateR=GENIE_LINK_IDLE;
             //       Serial.print("ACK ");
                    break;
                  case GENIE_NAK   :
                    genieStateR=ERROR_NAK;
                    Serial.print("RIGHT: NACK ");
                    break;    
                  default: 
                    Serial.print("RIGHT: received: ");
                    Serial.println(incomingByte, HEX);
                    break;
                }
              }
              // check elapsedMicros and millis timers
            
            ....
            other stuff happening
            ....
            
              if (sinceDisplayUpdate >=displayUpdateInterval) {
                sinceDisplayUpdate = sinceDisplayUpdate - displayUpdateInterval;
                updateDisplay();
              } 
            }
            The updateDisplay function checks if the state is idle and processes the queue:
            Code:
            void updateDisplay(){
              // handle queue for left display if state is idle
              if (genieStateL==GENIE_LINK_IDLE){
                // check if something in queue
                if (queueFirstIndexL>0 ){
                  if (queueFirstIndexL>0 and queueFirstIndexL<=16){             
                    // strings
                    displayText(queueFirstIndexL-1,0);
                  } else if  (queueFirstIndexL>16 and queueFirstIndexL<=24){    
                    // knobs
                    genieWriteObject(GENIE_OBJ_KNOB, queueFirstIndexL-17,parameterDisplay[queueFirstIndexL-17],0);
                  } else if (queueFirstIndexL==25){                        
                    // userImages0, plugin icon
                    genieWriteObject(GENIE_OBJ_USERIMAGES,0,selectedPluginIcon,0);
                  } else if (queueFirstIndexL==26){                        
                    // strings16, page
                    genieWriteObject(GENIE_OBJ_STRINGS,16,selectedPluginPage,0);
                  }
                  queueFirstIndexL=0; // reset and determine again
                  for (int i=1;i<=26;i++){
                    if (objectQueueL[i] > 0)  objectQueueL[i]--;
                    if (objectQueueL[i] == 1) queueFirstIndexL=i;
                  }
                  queueLastNoL--;
                }
              }
              // handle queue for right display if state is idle
              if (genieStateR==GENIE_LINK_IDLE){
                // check if something in queue
                if (queueFirstIndexR>0 ){
                  if (queueFirstIndexR>0 and queueFirstIndexR<=16){             
                    // strings
                    displayText(queueFirstIndexR-1,1);
                  } else if  (queueFirstIndexR>16 and queueFirstIndexR<=24){    
                    // knobs
                    genieWriteObject(GENIE_OBJ_KNOB, queueFirstIndexR-17,parameterDisplay[queueFirstIndexR-17+8],1);
                  } else if (queueFirstIndexR==25){                        
                    // strings21, track name
                    genieWriteStr(21,selectedTrackName,1);
                  } else if (queueFirstIndexR>=26 and queueFirstIndexR<=32){                        
                    // strings26-32, counter right to left
                    ledDigit[0] = ledText[queueFirstIndexR-23];
                    genieWriteStr(queueFirstIndexR,ledDigit,1);
                  } else if (queueFirstIndexR>=33 and queueFirstIndexR<=34){                        
                    // strings33-34, assignment right to left
                    genieWriteStr(queueFirstIndexR,characterTable[assignmentValue[queueFirstIndexR-33]],1);
                  } else if (queueFirstIndexR==35){                        
                    // strings16, mode
                    genieWriteObject(GENIE_OBJ_STRINGS,16,controlModeString,1);
                  }
                  queueFirstIndexR=0; // reset and determine again
                  for (int i=1;i<36;i++){
                    if (objectQueueR[i] > 0)  objectQueueR[i]--;
                    if (objectQueueR[i] == 1) queueFirstIndexR=i;
                  }
                  queueLastNoR--;
                }
              }
            }
            This function contains the knowledge on what type of object belongs to an index and calls the appropriate imported library function, for instance the genieWriteObject:
            Code:
            void genieWriteObject (uint16_t object, uint16_t index, uint16_t data, int genieDisplay) {
                uint16_t msb, lsb ;
                uint8_t checksum ;
                lsb = lowByte(data);
                msb = highByte(data);
                if (genieDisplay==0){
                  Serial2.write(GENIE_WRITE_OBJ) ;
                  checksum  = GENIE_WRITE_OBJ ;
                  Serial2.write(object) ;
                  checksum ^= object ;
                  Serial2.write(index) ;
                  checksum ^= index ;
                  Serial2.write(msb) ;
                  checksum ^= msb;
                  Serial2.write(lsb) ;
                  checksum ^= lsb;
                  Serial2.write(checksum) ;
                  genieStateL=GENIE_LINK_WFAN;
                }
                if (genieDisplay==1){
                  Serial3.write(GENIE_WRITE_OBJ) ;
                  checksum  = GENIE_WRITE_OBJ ;
                  Serial3.write(object) ;
                  checksum ^= object ;
                  Serial3.write(index) ;
                  checksum ^= index ;
                  Serial3.write(msb) ;
                  checksum ^= msb;
                  Serial3.write(lsb) ;
                  checksum ^= lsb;
                  Serial3.write(checksum) ;
                  genieStateR=GENIE_LINK_WFAN;
                }  
            }
            This is essentially the same as the library function with explicit ports and without the waitForIdle().

            There's definitely room for improvement as far as the code is concerned, I'm new to this language and still have a lot to learn but it works and I understand why it works

            The two displays update much faster and even if the displays cannot keep up this does not interfere with the updating of the DAW parameters.

            Kind regards,

            Gerrit







            Comment


            • #21
              Very cool! You learn a lot when you add responsiveness requirements to an application.

              Comment

              Working...
              X