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++.

Committer:
joeata2wh
Date:
Thu Mar 31 20:50:20 2016 +0000
Revision:
3:d06fb5798df1
Parent:
2:977f9a71c18a
Child:
4:b0212513533c
update link to sample code

Who changed what in which revision?

UserRevisionLine numberNew contents of line
joeata2wh 0:41457021be77 1 /* Serial command interface listens to inbound serial interupts
joeata2wh 0:41457021be77 2 saves the characters as they arrive and places a pointer to
joeata2wh 0:41457021be77 3 the last complete command in the last_cmd array and calls
joeata2wh 0:41457021be77 4 user callback when supplied. Can support simutaneous
joeata2wh 0:41457021be77 5 routing of input from multiple SIO ports. It considers
joeata2wh 0:41457021be77 6 \n or \r as command terminators The client is responsible
joeata2wh 0:41457021be77 7 to parse the command furhter if needed.
joeata2wh 0:41457021be77 8
joeata2wh 0:41457021be77 9 Intended use is for caller to periodically check for
joeata2wh 0:41457021be77 10 new commands and execute the last one found. Or caller
joeata2wh 0:41457021be77 11 can setup a callback so when a complete command which
joeata2wh 0:41457021be77 12 must be at least 1 character long between \n or \r
joeata2wh 0:41457021be77 13 characters.
joeata2wh 0:41457021be77 14
joeata2wh 0:41457021be77 15 ***
joeata2wh 0:41457021be77 16 * By Joseph Ellsworth CTO of A2WH
joeata2wh 0:41457021be77 17 * Take a look at A2WH.com Producing Water from Air using Solar Energy
joeata2wh 0:41457021be77 18 * March-2016 License: https://developer.mbed.org/handbook/MIT-Licence
joeata2wh 0:41457021be77 19 * Please contact us http://a2wh.com for help with custom design projects.
joeata2wh 0:41457021be77 20 ***
joeata2wh 0:41457021be77 21
joeata2wh 2:977f9a71c18a 22 Sample Use Example: https://developer.mbed.org/users/joeata2wh/code/xj-multi-serial-command-listener-example/
joeata2wh 1:b7992c722dcc 23
joeata2wh 1:b7992c722dcc 24 #include "mbed.h"
joeata2wh 1:b7992c722dcc 25 #include "multi-serial-command-listener.h"
joeata2wh 1:b7992c722dcc 26
joeata2wh 1:b7992c722dcc 27 Serial pc(USBTX, USBRX);
joeata2wh 1:b7992c722dcc 28 char myCommand[SCMD_MAX_CMD_LEN+1];
joeata2wh 1:b7992c722dcc 29
joeata2wh 1:b7992c722dcc 30 void commandCallback(char *cmdIn) {
joeata2wh 1:b7992c722dcc 31 strcpy(myCommand, cmdIn);
joeata2wh 1:b7992c722dcc 32 // all our commands will be recieved async in commandCallback
joeata2wh 1:b7992c722dcc 33 // we don't want to do time consuming things since it could
joeata2wh 1:b7992c722dcc 34 // block the reader and allow the uart to overflow so we simply
joeata2wh 1:b7992c722dcc 35 // copy it out in the callback and then process it latter.
joeata2wh 1:b7992c722dcc 36 }
joeata2wh 1:b7992c722dcc 37
joeata2wh 1:b7992c722dcc 38 int main() {
joeata2wh 1:b7992c722dcc 39 pc.baud(9600);
joeata2wh 1:b7992c722dcc 40 pc.printf("Demo multi-serial-command-listener\r\n");
joeata2wh 1:b7992c722dcc 41
joeata2wh 1:b7992c722dcc 42 // Instantiate our command processor for the
joeata2wh 1:b7992c722dcc 43 // USB serial line.
joeata2wh 1:b7992c722dcc 44 struct SCMD *cmdProc = scMake(&pc, commandCallback) ;
joeata2wh 1:b7992c722dcc 45
joeata2wh 1:b7992c722dcc 46 while(1) {
joeata2wh 1:b7992c722dcc 47 if (myCommand[0] != 0) {
joeata2wh 1:b7992c722dcc 48 pc.printf("Command Recieved =%s\r\n", myCommand);
joeata2wh 1:b7992c722dcc 49 myCommand[0] = 0; // clear until we recieve the next one
joeata2wh 1:b7992c722dcc 50 }
joeata2wh 1:b7992c722dcc 51 wait(0.05);
joeata2wh 1:b7992c722dcc 52 }
joeata2wh 1:b7992c722dcc 53 }
joeata2wh 1:b7992c722dcc 54
joeata2wh 1:b7992c722dcc 55
joeata2wh 1:b7992c722dcc 56 ** Basic Therory of Operation **
joeata2wh 0:41457021be77 57 The operation is the system will append new characters
joeata2wh 0:41457021be77 58 to the buffer until it hits the end. Whenever it sees
joeata2wh 0:41457021be77 59 a \r or \n will insert a \000 (null) so the previous
joeata2wh 0:41457021be77 60 characters can be used safely in strcpy.
joeata2wh 0:41457021be77 61
joeata2wh 3:d06fb5798df1 62 It will then set up a null terminator and call
joeata2wh 3:d06fb5798df1 63 the user specified callback.
joeata2wh 0:41457021be77 64
joeata2wh 0:41457021be77 65 To minimize risk of invalid data it is recomended the
joeata2wh 0:41457021be77 66 caller copies the last_cmd to local buffer using the
joeata2wh 0:41457021be77 67 sc_last_cmd_copy(char *dest) which disables interupts
joeata2wh 0:41457021be77 68 performs the copy and then re-enables the interupts.
joeata2wh 0:41457021be77 69 Otherwise a new character inbound could cause the
joeata2wh 0:41457021be77 70 command data to change out from under the user.
joeata2wh 0:41457021be77 71
joeata2wh 0:41457021be77 72 If the buffer fills up with more than SC_MAX_CMD_LEN
joeata2wh 0:41457021be77 73 characters what is already present will be treated
joeata2wh 0:41457021be77 74 as if it had encountered a \r or \n.
joeata2wh 0:41457021be77 75
joeata2wh 0:41457021be77 76 A cmd must contain at least 1 charcter or it will
joeata2wh 0:41457021be77 77 be ignored.
joeata2wh 0:41457021be77 78
joeata2wh 0:41457021be77 79 ** Multiple Listeners **
joeata2wh 0:41457021be77 80 The system will allow upto 10 serial listeners
joeata2wh 0:41457021be77 81 to each be processing new character. It will
joeata2wh 0:41457021be77 82 automatically multi-plex between these listeners.
joeata2wh 0:41457021be77 83 as needed. Each listner can have it's own cmd
joeata2wh 0:41457021be77 84 callback.
joeata2wh 0:41457021be77 85
joeata2wh 0:41457021be77 86 ** Known limitations **
joeata2wh 0:41457021be77 87 # If data arrives fast enough we could have sufficient
joeata2wh 0:41457021be77 88 data in uart buffer to contain multiple commands. In
joeata2wh 0:41457021be77 89 that instnace it is possible that commands before
joeata2wh 0:41457021be77 90 the last command could be over-written before
joeata2wh 0:41457021be77 91 calling code can process them.
joeata2wh 0:41457021be77 92
joeata2wh 0:41457021be77 93 # Can have SCMD_MAX_LISTENERS and no current report
joeata2wh 0:41457021be77 94 if new listener overflow is provied but it can be
joeata2wh 0:41457021be77 95 checked with sc_listener_ndx() which will return the
joeata2wh 0:41457021be77 96 index of the listener in queue or SCMD_LISTENER_NOT_FOUND
joeata2wh 0:41457021be77 97
joeata2wh 3:d06fb5798df1 98 # it takes some time to multi-plex across the listeners
joeata2wh 0:41457021be77 99 and check each one for a new character. Under very fast
joeata2wh 0:41457021be77 100 connection speeds it is possible that arriving data
joeata2wh 0:41457021be77 101 could overflow the uart buffer before we can copy the
joeata2wh 0:41457021be77 102 data out.
joeata2wh 0:41457021be77 103
joeata2wh 0:41457021be77 104 # There is no provision to detect the same Serial
joeata2wh 0:41457021be77 105 connection being used by more than one command
joeata2wh 0:41457021be77 106 listener. This is a problem because the first
joeata2wh 0:41457021be77 107 one in the listener queue will get all the data.
joeata2wh 0:41457021be77 108
joeata2wh 0:41457021be77 109 # No current detach processed for listener in
joeata2wh 0:41457021be77 110 sc_delete_listener which could increase interupt
joeata2wh 0:41457021be77 111 callback overhead. Need to research what happens
joeata2wh 0:41457021be77 112 with multiple attach calls.
joeata2wh 0:41457021be77 113
joeata2wh 1:b7992c722dcc 114 # Consumes at least SCMD_MAX_CMD_LEN memory one for
joeata2wh 1:b7992c722dcc 115 inbound buffer.
joeata2wh 0:41457021be77 116
joeata2wh 0:41457021be77 117 ** References **
joeata2wh 0:41457021be77 118 https://developer.mbed.org/cookbook/Serial-Interrupts
joeata2wh 0:41457021be77 119
joeata2wh 1:b7992c722dcc 120 NOTE: I am using a struct instead of a class here
joeata2wh 1:b7992c722dcc 121 because I may need to port to PSoC in near future
joeata2wh 1:b7992c722dcc 122 and it is unclear when they will get full C++.
joeata2wh 1:b7992c722dcc 123
joeata2wh 1:b7992c722dcc 124 ** TODO **
joeata2wh 1:b7992c722dcc 125 Modify the callbacks so they are queued for a timer
joeata2wh 1:b7992c722dcc 126 that sends the actual callback to the command processor
joeata2wh 1:b7992c722dcc 127 rather than blocking the uart read loop.
joeata2wh 2:977f9a71c18a 128
joeata2wh 2:977f9a71c18a 129 Separate into .h, .c files.
joeata2wh 0:41457021be77 130 */
joeata2wh 0:41457021be77 131
joeata2wh 0:41457021be77 132 #ifndef serial_command_H
joeata2wh 0:41457021be77 133 #define serial_command_H
joeata2wh 0:41457021be77 134
joeata2wh 0:41457021be77 135
joeata2wh 0:41457021be77 136 #define SCMD_MAX_LISTENERS 10
joeata2wh 0:41457021be77 137 #define SCMD_MAX_CMD_LEN 35
joeata2wh 0:41457021be77 138 #define SCMD_TOO_MANY_LISTENER -2
joeata2wh 0:41457021be77 139 #define SCMD_LISTENER_NOT_FOUND -1
joeata2wh 0:41457021be77 140
joeata2wh 0:41457021be77 141 void sc_rx_interrupt();
joeata2wh 0:41457021be77 142
joeata2wh 0:41457021be77 143 struct SCMD {
joeata2wh 0:41457021be77 144 Serial *sio;
joeata2wh 1:b7992c722dcc 145 char buff[SCMD_MAX_CMD_LEN+1];
joeata2wh 0:41457021be77 146 short in_ndx;
joeata2wh 0:41457021be77 147 char last_char;
joeata2wh 0:41457021be77 148 void (*callback)(char *);
joeata2wh 0:41457021be77 149 };
joeata2wh 0:41457021be77 150
joeata2wh 0:41457021be77 151 struct SCMD *sc_listeners[SCMD_MAX_LISTENERS]; // set up to support upto 10 listeners
joeata2wh 0:41457021be77 152
joeata2wh 0:41457021be77 153 // Add a listener to queue for processing
joeata2wh 0:41457021be77 154 // so it receives interupts from the Serial
joeata2wh 0:41457021be77 155 // IO in wrk->sio
joeata2wh 0:41457021be77 156 int sc_add_listener(struct SCMD *wrk) {
joeata2wh 0:41457021be77 157 short ndx;
joeata2wh 0:41457021be77 158 for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
joeata2wh 0:41457021be77 159 if (sc_listeners[ndx] == wrk) {
joeata2wh 0:41457021be77 160 return ndx; // listener already present
joeata2wh 0:41457021be77 161 }
joeata2wh 0:41457021be77 162 if (sc_listeners[ndx] == NULL) {
joeata2wh 0:41457021be77 163 sc_listeners[ndx] = wrk; // take available slot
joeata2wh 0:41457021be77 164 return ndx;
joeata2wh 0:41457021be77 165 }
joeata2wh 0:41457021be77 166 }
joeata2wh 0:41457021be77 167 return SCMD_TOO_MANY_LISTENER;
joeata2wh 0:41457021be77 168 }
joeata2wh 0:41457021be77 169
joeata2wh 0:41457021be77 170 // Removes listener from queue and frees up it's
joeata2wh 0:41457021be77 171 // memory. After calling this the wrk pointer
joeata2wh 0:41457021be77 172 // will no longer be valid. Returns the index
joeata2wh 0:41457021be77 173 // in queue where it found the listener or
joeata2wh 0:41457021be77 174 // SCMD_LISTENER_NOT_FOUND if it was not found
joeata2wh 0:41457021be77 175 // in queue.
joeata2wh 0:41457021be77 176 int sc_delete_listener(struct SCMD *wrk) {
joeata2wh 0:41457021be77 177 short ndx;
joeata2wh 0:41457021be77 178 for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
joeata2wh 0:41457021be77 179 if (sc_listeners[ndx] == wrk) {
joeata2wh 0:41457021be77 180 sc_listeners[ndx] = NULL;
joeata2wh 0:41457021be77 181 free(wrk);
joeata2wh 0:41457021be77 182 return ndx; // listener already present
joeata2wh 0:41457021be77 183 }
joeata2wh 0:41457021be77 184 }
joeata2wh 0:41457021be77 185 free(wrk);
joeata2wh 0:41457021be77 186 return SCMD_LISTENER_NOT_FOUND;
joeata2wh 0:41457021be77 187 }
joeata2wh 0:41457021be77 188
joeata2wh 0:41457021be77 189
joeata2wh 0:41457021be77 190 // returns the index in queue where the wrk
joeata2wh 0:41457021be77 191 // listener is found or SCMD_LISTENER_NOT_FOUND
joeata2wh 0:41457021be77 192 // if not present in the queue.
joeata2wh 0:41457021be77 193 int sc_listener_ndx(struct SCMD *wrk) {
joeata2wh 0:41457021be77 194 short ndx;
joeata2wh 0:41457021be77 195 for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
joeata2wh 0:41457021be77 196 if (sc_listeners[ndx] == wrk) {
joeata2wh 0:41457021be77 197 return ndx; // listener already present
joeata2wh 0:41457021be77 198 }
joeata2wh 0:41457021be77 199 }
joeata2wh 0:41457021be77 200 return SCMD_LISTENER_NOT_FOUND;
joeata2wh 0:41457021be77 201 }
joeata2wh 0:41457021be77 202
joeata2wh 0:41457021be77 203
joeata2wh 0:41457021be77 204 // constuct a new listener for the specified
joeata2wh 0:41457021be77 205 // serial IO and add it to the listen queue
joeata2wh 0:41457021be77 206 // to read inbound data on that port.
joeata2wh 0:41457021be77 207 struct SCMD *scMake(Serial *sio, void (*callback)(char *)) {
joeata2wh 0:41457021be77 208 struct SCMD *sc = (struct SCMD *) malloc(sizeof(struct SCMD));
joeata2wh 0:41457021be77 209 sc->sio = sio;
joeata2wh 1:b7992c722dcc 210 memset(sc->buff,SCMD_MAX_CMD_LEN+1,0);
joeata2wh 0:41457021be77 211 sc->callback = callback;
joeata2wh 0:41457021be77 212 sc_add_listener(sc);
joeata2wh 0:41457021be77 213 sc->sio->attach(&sc_rx_interrupt, Serial::RxIrq);
joeata2wh 0:41457021be77 214 return sc;
joeata2wh 0:41457021be77 215 }
joeata2wh 0:41457021be77 216
joeata2wh 0:41457021be77 217
joeata2wh 0:41457021be77 218 // process any inbound characters available
joeata2wh 0:41457021be77 219 // in the uart buffer for the Serial device
joeata2wh 0:41457021be77 220 // associated with wrk->sio. Also detects
joeata2wh 0:41457021be77 221 // end of command and call the command
joeata2wh 0:41457021be77 222 // processor callback.
joeata2wh 0:41457021be77 223 void sc_rx_process(struct SCMD *wrk) {
joeata2wh 0:41457021be77 224 // Loop just in case more than one character is in UART's receive FIFO buffer
joeata2wh 0:41457021be77 225 // Stop if buffer full
joeata2wh 0:41457021be77 226 while ((wrk->sio->readable())) {
joeata2wh 0:41457021be77 227 char cin = wrk->sio->getc();
joeata2wh 1:b7992c722dcc 228 //printf("cin=%d\r\n", (int) cin);
joeata2wh 1:b7992c722dcc 229 if ((cin == 10) || (cin == 13)) { // found CR or LF
joeata2wh 0:41457021be77 230 if (wrk->in_ndx > 0) {
joeata2wh 0:41457021be77 231 // commands must be at least 1 byte long
joeata2wh 1:b7992c722dcc 232 wrk->buff[wrk->in_ndx] = 0; // add terminating null
joeata2wh 1:b7992c722dcc 233 wrk->callback(wrk->buff);
joeata2wh 0:41457021be77 234 }
joeata2wh 1:b7992c722dcc 235 wrk->in_ndx = 0; // reset for next cycle;
joeata2wh 1:b7992c722dcc 236 wrk->buff[0] = 0; // add null terminator
joeata2wh 0:41457021be77 237 }
joeata2wh 0:41457021be77 238 else {
joeata2wh 1:b7992c722dcc 239 // not a CR or LF so must be a valid character
joeata2wh 0:41457021be77 240 wrk->buff[wrk->in_ndx] = cin; // add character to the buffer
joeata2wh 0:41457021be77 241 wrk->buff[wrk->in_ndx + 1] = 0; // add null terminator just in case
joeata2wh 1:b7992c722dcc 242 wrk->in_ndx++;
joeata2wh 1:b7992c722dcc 243 //printf("wrk->buff=%s in_ndx=%d\n", wrk->buff, wrk->in_ndx);
joeata2wh 0:41457021be77 244 if (wrk->in_ndx >= SCMD_MAX_CMD_LEN) {
joeata2wh 0:41457021be77 245 // buffer is full so treat as command
joeata2wh 1:b7992c722dcc 246 wrk->callback(wrk->buff);
joeata2wh 0:41457021be77 247 wrk->buff[0] = 0;
joeata2wh 1:b7992c722dcc 248 wrk->in_ndx = 0;
joeata2wh 0:41457021be77 249 // add callback here
joeata2wh 0:41457021be77 250 }
joeata2wh 0:41457021be77 251 }
joeata2wh 0:41457021be77 252 }
joeata2wh 0:41457021be77 253 return;
joeata2wh 0:41457021be77 254 }
joeata2wh 0:41457021be77 255
joeata2wh 0:41457021be77 256 // Process all characters available in all the
joeata2wh 0:41457021be77 257 // uart buffers for our listeners
joeata2wh 0:41457021be77 258 void sc_rx_interrupt() {
joeata2wh 0:41457021be77 259 int ndx;
joeata2wh 0:41457021be77 260 for (ndx=0; ndx < SCMD_MAX_LISTENERS; ndx++) {
joeata2wh 0:41457021be77 261 if (sc_listeners[ndx] != NULL) {
joeata2wh 0:41457021be77 262 sc_rx_process(sc_listeners[ndx]);
joeata2wh 0:41457021be77 263 }
joeata2wh 0:41457021be77 264 }
joeata2wh 0:41457021be77 265 return;
joeata2wh 0:41457021be77 266 }
joeata2wh 0:41457021be77 267
joeata2wh 0:41457021be77 268 #endif