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 22:15:02 2016 +0000
Revision:
5:294ac985aded
Parent:
4:b0212513533c
update comments for minor API change

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