Command listener for serial ports capable of listening to multiple ports simultaneously. It considered \n \r command terminators and will make callback to client code when it detects completed commands. Uses serial interrupts to receive the data.

Dependents:   xj-multi-serial-command-listener-example xej-Nucleo-F401RE-and-HC05-Bluetooth data_log

Event based serial command listener

By Joseph Ellsworth CTO of A2WH Take a look at A2WH.com Producing Water from Air using Solar Energy March-2016 License: https://developer.mbed.org/handbook/MIT-Licence Please contact us http://a2wh.com for help with custom design projects.

Sample Use

Sample Use Example: https://developer.mbed.org/users/joeata2wh/code/xj-multi-serial-command-listener-example/

sample code

multi serial uart command listener with callback

  #include "mbed.h"
  #include "multi-serial-command-listener.h"

  Serial pc(USBTX, USBRX);
  char myCommand[SCMD_MAX_CMD_LEN+1];

  void commandCallback(char *cmdIn) {
    strcpy(myCommand, cmdIn);
    // all our commands will be recieved async in commandCallback
    // we don't want to do time consuming things since it could
    // block the reader and allow the uart to overflow so we simply 
    // copy it out in the callback and then process it latter. 
  }

  int main() {
    pc.baud(9600);
    pc.printf("Demo multi-serial-command-listener\r\n");
    
    // Instantiate our command processor for the 
    // USB serial line. 
    struct SCMD *cmdProc = scMake(&pc, commandCallback)  ;
    
    while(1) {        
        if (myCommand[0] != 0) {     
          pc.printf("Command Recieved =%s\r\n", myCommand);
          myCommand[0] = 0; // clear until we recieve the next one
        }
        wait(0.05);
    }
  }

Basic Theory of Operation

The operation is the system will append new characters to the buffer until it hits the end. Whenever it sees a \r or \n will insert a \000 (null) so the previous characters can be used safely in strcpy.

It will then set up a null terminator and call the user specified callback.

To minimize risk of invalid data it is recommended the caller copies the last_cmd to local buffer using the sc_last_cmd_copy(char *dest) which disables interrupts performs the copy and then re-enables the interrupts. Otherwise a new character inbound could cause the command data to change out from under the user.

If the buffer fills up with more than SC_MAX_CMD_LEN characters what is already present will be treated as if it had encountered a \r or \n.

A command must contain at least 1 character or it will be ignored.

Multiple Listeners

The system will allow up-to 10 serial listeners to each be processing new character. It will automatically multiplex between these listeners. as needed. Each listener can have it's own command callback.

Known limitations

  1. If data arrives fast enough we could have sufficient data in UART buffer to contain multiple commands. In that instance it is possible that commands before the last command could be over-written before calling code can process them.
  1. Can have SCMD_MAX_LISTENERS and no current report if new listener overflow is provided but it can be checked with sc_listener_ndx() which will return the index of the listener in queue or SCMD_LISTENER_NOT_FOUND
  1. it takes some time to multiplex across the listeners and check each one for a new character. Under very fast connection speeds it is possible that arriving data could overflow the UART buffer before we can copy the data out.
  1. There is no provision to detect the same Serial connection being used by more than one command listener. This is a problem because the first one in the listener queue will get all the data.
  1. No current detach processed for listener in sc_delete_listener which could increase interrupt callback overhead. Need to research what happens with multiple attach calls.
  1. Consumes at least SCMD_MAX_CMD_LEN memory one for inbound buffer.

References

https://developer.mbed.org/cookbook/Serial-Interrupts

Disclaimer

NOTE: I am using a struct instead of a class here because I may need to port to PSoC in near future and it is unclear when they will get full C++.

Revision:
0:41457021be77
Child:
1:b7992c722dcc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/multi-serial-command-listener.h	Thu Mar 31 19:17:07 2016 +0000
@@ -0,0 +1,226 @@
+/* Serial command interface listens to inbound serial interupts
+  saves the characters as they arrive and places a pointer to
+  the last complete command in the last_cmd array and calls
+  user callback when supplied.   Can support simutaneous
+  routing of input from multiple SIO ports. It considers
+  \n or \r as command terminators The client is responsible
+  to parse the command furhter if needed. 
+     
+  Intended use is for caller to periodically check for
+  new commands and execute the last one found.  Or caller
+  can setup a callback so when a complete command which
+  must be at least 1 character long between \n or \r 
+  characters. 
+  
+  ***
+  * By Joseph Ellsworth CTO of A2WH
+  * Take a look at A2WH.com Producing Water from Air using Solar Energy
+  * March-2016 License: https://developer.mbed.org/handbook/MIT-Licence 
+  * Please contact us http://a2wh.com for help with custom design projects.
+  ***
+  
+  ** Basic Operation **
+  The operation is the system will append new characters
+  to the buffer until it hits the end.  Whenever it sees 
+  a \r or \n will insert a \000 (null) so the previous
+  characters can be used safely in strcpy. 
+  
+  It will then copy upto SCMD_MAX_CMD_LEN bytes into 
+  SCMD->last_cmd and left shift the data in input
+  buffer to begin accumulating the next command.
+  
+  To minimize risk of invalid data it is recomended the
+  caller copies the last_cmd to local buffer using the 
+  sc_last_cmd_copy(char *dest) which disables interupts 
+  performs the copy and then re-enables the interupts. 
+  Otherwise a new character inbound could cause the
+  command data to change out from under the user. 
+  
+  If the buffer fills up with more than SC_MAX_CMD_LEN
+  characters what is already present will be treated
+  as if it had encountered a \r or \n.
+  
+  A cmd must contain at least 1 charcter or it will 
+  be ignored. 
+  
+  ** Multiple Listeners **
+  The system will allow  upto 10 serial listeners
+  to each be processing new character.   It will
+  automatically multi-plex between these listeners.
+  as needed.  Each listner can have it's own cmd 
+  callback. 
+  
+  ** Known limitations **
+  # If data arrives fast enough we could have sufficient
+  data in uart buffer to contain multiple commands. In 
+  that instnace it is possible that commands before
+  the last command could be over-written before
+  calling code can process them. 
+  
+  # Can have SCMD_MAX_LISTENERS and no current report
+   if new listener overflow is provied but it can be
+   checked with sc_listener_ndx() which will return the
+   index of the listener in queue or SCMD_LISTENER_NOT_FOUND
+   
+  # it takes some time to mult-plex across the listeners
+   and check each one for a new character. Under very fast
+   connection speeds it is possible that arriving data
+   could overflow the uart buffer before we can copy the
+   data out. 
+   
+  # There is no provision to detect the same Serial 
+   connection being used by more than one command 
+   listener.   This is a problem because the first
+   one in the listener queue will get all the data.
+   
+  # No current detach processed for listener in 
+  sc_delete_listener which could increase interupt
+  callback overhead.  Need to research what happens
+  with multiple attach calls. 
+   
+  # Consumes at least 2 X SCMD_MAX_CMD_LEN memory one for
+    inbound buffer and one for last completed command.
+   
+  ** References **
+    https://developer.mbed.org/cookbook/Serial-Interrupts
+  
+  
+*/
+
+#ifndef serial_command_H
+#define serial_command_H
+
+
+#define SCMD_MAX_LISTENERS 10
+#define SCMD_MAX_CMD_LEN 35
+#define SCMD_TOO_MANY_LISTENER -2
+#define SCMD_LISTENER_NOT_FOUND -1
+
+void sc_rx_interrupt();
+
+struct SCMD {
+   Serial *sio;
+   char buff[SCMD_MAX_CMD_LEN+1];
+   char last_cmd[SCMD_MAX_CMD_LEN+1];   
+   short in_ndx;
+   char last_char;
+   void (*callback)(char *);
+};
+
+struct SCMD *sc_listeners[SCMD_MAX_LISTENERS]; // set up to support upto 10 listeners 
+
+// Add a listener to queue for processing
+// so it receives interupts from the Serial
+// IO in wrk->sio
+int sc_add_listener(struct SCMD *wrk) {
+  short ndx;
+  for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
+     if (sc_listeners[ndx] == wrk) {
+       return ndx; // listener already present
+     }
+     if (sc_listeners[ndx] == NULL) {
+         sc_listeners[ndx] = wrk; // take available slot
+         return ndx;
+     }
+  }    
+  return SCMD_TOO_MANY_LISTENER;
+}
+
+// Removes listener from queue and frees up it's 
+// memory.  After calling this the wrk pointer 
+// will no longer be valid.  Returns the index
+// in queue where it found the listener or
+// SCMD_LISTENER_NOT_FOUND if it was not found
+// in queue.
+int sc_delete_listener(struct SCMD *wrk) {
+  short ndx;
+  for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
+     if (sc_listeners[ndx] == wrk) {
+       sc_listeners[ndx] = NULL;
+       free(wrk);
+       return ndx; // listener already present
+     }
+  }      
+  free(wrk);
+  return SCMD_LISTENER_NOT_FOUND;
+}
+
+
+// returns the index in queue where the wrk
+// listener is found or SCMD_LISTENER_NOT_FOUND 
+// if not present in the queue. 
+int sc_listener_ndx(struct SCMD *wrk) {
+  short ndx;
+  for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
+     if (sc_listeners[ndx] == wrk) {       
+       return ndx; // listener already present
+     }
+  }      
+  return SCMD_LISTENER_NOT_FOUND;
+}
+
+
+// constuct a new listener for the specified
+// serial IO and add it to the listen queue
+// to read inbound data on that port.
+struct SCMD *scMake(Serial *sio, void (*callback)(char *)) {
+  struct SCMD *sc = (struct SCMD *)  malloc(sizeof(struct SCMD));
+  sc->sio = sio;
+  memset(sc->buff,SCMD_MAX_CMD_LEN+1,0);
+  memset(sc->last_cmd,SCMD_MAX_CMD_LEN+1,0);
+  sc->callback = callback;
+  sc_add_listener(sc);
+  sc->sio->attach(&sc_rx_interrupt, Serial::RxIrq);  
+  return sc;
+}
+
+
+// process any inbound characters available
+// in the uart buffer for the Serial device
+// associated with wrk->sio.  Also detects
+// end of command and call the command 
+// processor callback. 
+void sc_rx_process(struct SCMD *wrk) {   
+  // Loop just in case more than one character is in UART's receive FIFO buffer
+  // Stop if buffer full
+    while ((wrk->sio->readable())) {
+       char cin = wrk->sio->getc();
+       if ((cin == 10) || (cin == 13)) { // found CR or LF
+         wrk->buff[wrk->in_ndx] = 0; // add null terminator
+         if (wrk->in_ndx > 0) {
+           // commands must be at least 1 byte long
+           strcpy(wrk->last_cmd, wrk->buff);
+           wrk->in_ndx = 0;
+           // add callback here 
+         }
+       }
+       else {
+          // not a CR or LF so must be a valid character
+          wrk->in_ndx++;
+          wrk->buff[wrk->in_ndx] = cin; // add character to the buffer 
+          wrk->buff[wrk->in_ndx + 1] = 0; // add null terminator just in case          
+          if (wrk->in_ndx >= SCMD_MAX_CMD_LEN) { 
+           // buffer is full so treat as command
+           strcpy(wrk->last_cmd, wrk->buff);           
+           wrk->buff[0] = 0;           
+           wrk->in_ndx = 0;  
+           // add callback here         
+          }
+       }           
+    }
+    return;
+}
+
+// Process all characters available in all the
+// uart buffers for our listeners
+void sc_rx_interrupt() {
+  int ndx;
+  for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
+     if (sc_listeners[ndx] != NULL) {       
+       sc_rx_process(sc_listeners[ndx]);
+     }
+  }      
+  return;  
+}
+
+#endif
\ No newline at end of file