Monday, November 5, 2018

Messages

I added an a couple of interface methods to the underlying HardwareSerial class -- it's possible that I could have done this by extension rather than hacking, but it might have been even harder to integrate. The msgAvailable() method returns how many of some specified character -- which should be set using setMsgTerm(uint8_t) -- have been received. In usual practice this is a count of newlines, where a newline means that the source has completed a text formatted message. This makes certain that the receiving Arduino isn't waiting around for characters when processing messages, which is a bit of an improvement on the standard Serial.available() method.

The msgAvailable() method is probably not really useful unless you are implementing a higher speed call-and-response system of some kind. It requires some heavier internals hacking than the scheduler and ADC fixes -- which have to be done on every new Arduino version -- so I would recommend waiting until your mileage indicates that it's needed before trying it out....

But first... A little about messaging....

 I puzzled over I/O text parsing methods like Serial.parseInt() and finally realized that I could find no way to use that parsing on my own message strings. Therefore I gave in and tried to make it work as is. One doesn't necessarily need my Task Scheduler code to do this, but it fits in fairly nicely.

Each pass through the loop() method looks to see if there are some characters available -- hopefully a full message, where one could use msgAvailable() to be absolutely sure -- and posts (or just executes) a message receiving task:

    // USB message function
    // if we have a new message, post the receive task
    // if the message isn't handled by the time this runs again
    //  we will get a second Task post, so get with it...
    // here we just check available()
    //  and hope we have a whole message
    // do we have more than one char available?
    byte nMsg = (Serial.available() > 1) ? 1 : 0;
    if( nMsg != 0 )
        postTask( 0, messageTask, nMsg );
        // note that this could also just execute the method....
        // messageTask( nMsg );

When executed, the messageTask() reads and parses values from the Serial input, then executes user functions as needed.

Message Example

I usually use messages with a single character command followed by integer or string parameters, all ASCII with white-space (space or tab) delimiters, followed by a newline ('\n' in C) terminator that says it's all done, ala:

    r 1234 5678\n

The Message Task parses these parameters and get stuff rolling. Two interesting(?) things about functions like Serial.parseInt() are,
  1. They skip white-space until they find characters they like, then consume those chars until they find more white-space or chars they don't like, and stop there;
  2. If they don't find anything they like, they block for up to one second waiting ...which is annoying...

But used judiciously these functions can collect parameter values, such as:

    /** Task posted when a serial message is found
     * @param n -- ignored...
     */
    void messageTask( uint16_t n )
    {
        // get the first command character
        char val = Serial.read();
   
        // see what we got, for debugging, etc
        //Serial.print( "cmd: " );
        //Serial.print( val );

        // Perform an action depending on the command
       switch( val )
      {
         case 'r': // run
         case 'R':
           // collect a variable number of arguments
           // -- up to 10 before crashing --
           // assuming that only space is used as a delimiter
           // and not handling trailing spaces very well at all...
           // ... if the next char is a space
           //   presume we have a new parameter...
           int args[10];
           byte count = 0;
           while( Serial.peek() == ' ' )
           {
                args[count] = (char) Serial.parseInt();

                // optional debuging
                //    Serial.print( ' ' );
                //    Serial.print( args[count] );

               ++count;
          }

          // do something with it all...
          runMe( args[0], args[1], ... );
        break;

        // .... //

        default:
            ; // de Nada
      }

      // optional debuging terminate
      //Serial.println();

      // consume anything else including the terminator
      // note: this is important, so we don't get called again
      // with just the terminator in the message input buffer
      do
      {
           val = Serial.read();

      } while( (val != '\n') && (val != -1) );

      return;
    }

Status Return

For return status messages the same format can be used and the Serial.print() or printf() functions work fine. It's not imperative that you use the newline terminator but it probably makes things much easier at the other end.

So there you go...

I've putzed around with raw binary (non-ASCII) messages, but the parsing is less flexible, terminators are hard to come by, byte ordering can still be problematic, and just plain text is much easier to monitor on the host side during development.


Next we'll look at the overall program structure.

No comments:

Post a Comment