2 // Product: Serial command/response HSM (Samek, Orthoganol Component pattern)
4 // Date: 30 November 2010
5 // Author: Harry McNally
7 // +-----------------------+
8 // | d e c i s i o n s |
9 // +-----------------------+
10 // | a n d d e s i g n s |
11 // +-----------------------+
13 // Copyright (C) 2010 Decisions and Designs Pty Ltd. All rights reserved.
15 // This software may be distributed and modified under the terms of the GNU
16 // General Public License version 2 (GPL) as published by the Free Software
17 // Foundation and appearing in the file GPL2.TXT included in the packaging of
18 // this file. Please note that GPL Section 2[b] requires that all works based
19 // on this software must also be made publicly available under the terms of
20 // the GPL ("Copyleft").
22 // Contact information:
23 // Decisions and Designs Web site: http://www.decisions-and-designs.com.au
27 #include "serial_command.h" /* SerialCommand class for Container VArg access */
28 #include "serial_io.h"
32 #define COMMAND_VARGS 4
35 /* SerialCommand states -----------------------------------------------------*/
37 static QState SerialCommand_initial(SerialCommand *me);
38 static QState SerialCommand_operational(SerialCommand *me);
39 static QState SerialCommand_parsing(SerialCommand *me);
40 static QState SerialCommand_startArgument(SerialCommand *me);
41 static QState SerialCommand_firstArgumentDigit(SerialCommand *me);
42 static QState SerialCommand_processArgumentDigits(SerialCommand *me);
43 static QState SerialCommand_awaitSeparator(SerialCommand *me);
44 static QState SerialCommand_awaitTermination(SerialCommand *me);
45 static QState SerialCommand_failTermination(SerialCommand *me);
48 void serialClearVargs(SerialCommand *me);
49 void serialInitialiseVarg(SerialCommand *me, uint8_t type);
50 uint8_t serialProcessDigit(SerialCommand *me, uint8_t ch);
51 void serialFinishVarg(SerialCommand *me);
53 /* HSM constructor .........................................................*/
54 void SerialCommand_ctor(SerialCommand *me, QActive *container, Menu *menu,
55 MenuSignal *menuSignals, char *txBuffer, uint8_t txSize,
\r
56 uint8_t port, uint32_t baud) {
57 me->container = container;
59 me->menuSignals = menuSignals;
\r
60 me->txBuffer = txBuffer;
\r
62 QActive_ctor((QActive *)me, (QStateHandler)&SerialCommand_initial);
65 me->serialTransmit = serialTransmit;
66 me->serialTransmitComplete = serialTransmitComplete;
67 initSerial(baud, (QActive *)container, (QActive *)container);
70 #if defined (__AVR_ATmega1280__)
72 me->serialTransmit = serial1Transmit;
73 me->serialTransmitComplete = serial1TransmitComplete;
74 initSerial1(baud, (QActive *)container, (QActive *)container);
78 me->serialTransmit = serial2Transmit;
79 me->serialTransmitComplete = serial2TransmitComplete;
80 initSerial2(baud, (QActive *)container, (QActive *)container);
84 me->serialTransmit = serial3Transmit;
85 me->serialTransmitComplete = serial3TransmitComplete;
86 initSerial3(baud, (QActive *)container, (QActive *)container);
92 /* HSM definition ----------------------------------------------------------*/
93 QState SerialCommand_initial(SerialCommand *me) {
94 return Q_TRAN(&SerialCommand_operational);
96 /*..........................................................................*/
97 QState SerialCommand_operational(SerialCommand *me) {
100 return Q_TRAN(&SerialCommand_parsing);
103 /* initialise the tx buffer and varg counter */
104 me->txHead = me->txTail = 0;
109 case SERIAL_TX_EMPTY_SIG : {
110 /* no characters left in buffer to transmit */
111 if (me->txHead == me->txTail) {
114 /* if non-zero (busy) returned, signal was stale, wait for next */
115 if ((*me->serialTransmit)(*(me->txBuffer + me->txTail))) {
118 /* else character is transmitting, update tail index */
120 if (me->txTail >= me->txSize)
124 /* command rx errors wait for next CR/LF and NACK */
125 case SERIAL_RX_ERROR_SIG : {
126 return Q_TRAN(&SerialCommand_failTermination);
129 return Q_SUPER(&QHsm_top);
131 /*..........................................................................*/
132 QState SerialCommand_parsing(SerialCommand *me) {
135 me->parser = me->menu;
139 case SERIAL_RX_DATA_SIG : {
140 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
141 /* command terminating CR or LF */
142 if ((ch == '\r') || (ch == '\n')) {
143 /* end of command not possible at menu root so drop ch */
144 if (me->parser == me->menu) {
147 /* unexpected early termination, send 0th menuSignal (fail) */
148 QActive_post(me->container, me->menuSignals->sig, 0);
149 return Q_TRAN(&SerialCommand_operational);
151 /* scan the menu tree. transition to await CR/LF if no match */
152 while (me->parser->idx) {
153 /* search for matching char in menu */
154 if (ch == me->parser->ch) {
156 if (me->parser->idx & EOC) {
157 /* no vargs to fetch */
\r
158 MenuSignal *msig = me->menuSignals;
159 msig += me->parser->idx & ~EOC;
160 if (!(msig->varg & (VARG_TYPE << 0))) {
\r
161 return Q_TRAN(&SerialCommand_awaitTermination);
\r
163 /* fail if container has not released vargs in time */
165 return Q_TRAN(&SerialCommand_failTermination);
167 /* prepare and go fetch any vargs */
168 serialClearVargs(me);
169 return Q_TRAN(&SerialCommand_startArgument);
172 /* index to the next menu sub-list */
173 me->parser = me->menu + me->parser->idx;
177 /* next entry in menu list */
180 /* no match for ch so await CR/LF and then signal failure */
181 return Q_TRAN(&SerialCommand_failTermination);
184 return Q_SUPER(&SerialCommand_operational);
186 /*..........................................................................*/
187 QState SerialCommand_startArgument(SerialCommand *me) {
191 /* unpack the two bit VARG type; one of four in menuSignal byte */
192 type = (me->menuSignals + (me->parser->idx & ~EOC))->varg;
193 type >>= me->vidx << 1;
195 serialInitialiseVarg(me, type);
198 case SERIAL_RX_DATA_SIG : {
199 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
200 if ((ch == '\r') || (ch == '\n')) {
201 /* unexpected early termination, send 0th menuSignal (fail) */
202 QActive_post(me->container, me->menuSignals->sig, 0);
203 return Q_TRAN(&SerialCommand_operational);
206 if ((ch == ' ') || (ch == '\t')) {
212 return Q_TRAN(&SerialCommand_firstArgumentDigit);
215 /* negate on - prefix */
216 me->vargs[me->vidx].type |= VARG_NEGATIVE;
217 return Q_TRAN(&SerialCommand_firstArgumentDigit);
219 if ((ch == 'x') || (ch == 'X')) {
220 /* x or X prefix a hex number */
221 me->vargs[me->vidx].type |= VARG_HEX;
222 return Q_TRAN(&SerialCommand_firstArgumentDigit);
224 /* failure to consume char as digit returns non-zero */
225 if (serialProcessDigit(me, ch)) {
\r
226 serialClearVargs(me);
227 return Q_TRAN(&SerialCommand_failTermination);
229 /* successfully accumulated first number */
230 return Q_TRAN(&SerialCommand_processArgumentDigits);
233 return Q_SUPER(&SerialCommand_operational);
235 /*..........................................................................*/
236 QState SerialCommand_firstArgumentDigit(SerialCommand *me) {
238 case SERIAL_RX_DATA_SIG : {
239 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
240 if ((ch == '\r') || (ch == '\n')) {
241 /* unexpected early termination, send 0th menuSignal (fail) */
242 QActive_post(me->container, me->menuSignals->sig, 0);
\r
243 serialClearVargs(me);
244 return Q_TRAN(&SerialCommand_operational);
246 /* failure to consume char as digit returns non-zero */
247 if (serialProcessDigit(me, ch)) {
\r
248 serialClearVargs(me);
249 return Q_TRAN(&SerialCommand_failTermination);
251 /* Successfully accumulated first number */
252 return Q_TRAN(&SerialCommand_processArgumentDigits);
255 return Q_SUPER(&SerialCommand_operational);
257 /*..........................................................................*/
258 QState SerialCommand_processArgumentDigits(SerialCommand *me) {
260 case SERIAL_RX_DATA_SIG : {
261 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
262 MenuSignal *msig = me->menuSignals;
263 msig += me->parser->idx & ~EOC;
264 if ((ch=='\r')||(ch=='\n')||(ch==' ')||(ch==',')||(ch=='\t')) {
265 serialFinishVarg(me);
266 me->vidx++; /* final vargs count */
267 /* still more arguments to fetch */
268 if ((me->vidx < VARG_COUNT) &&
269 (msig->varg & (VARG_TYPE << (me->vidx << 1)))) {
270 if ((ch == ' ') || (ch == '\t')) {
272 /* space is the separator */
273 return Q_TRAN(&SerialCommand_startArgument);
275 /* a separator may follow */
276 return Q_TRAN(&SerialCommand_awaitSeparator);
280 return Q_TRAN(&SerialCommand_startArgument);
282 /* CR/LF, early termination, send 0th menuSignal (fail) */
283 QActive_post(me->container, me->menuSignals->sig, 0);
\r
284 serialClearVargs(me);
285 return Q_TRAN(&SerialCommand_operational);
287 /* all arguments accumulated */
289 if ((ch == ' ') || (ch == '\t')) {
291 /* expected terminator so fail */
292 QActive_post(me->container, me->menuSignals->sig, 0);
293 return Q_TRAN(&SerialCommand_operational);
295 /* trailing space is valid */
296 return Q_TRAN(&SerialCommand_awaitTermination);
300 /* separator is invalid so fail */
301 QActive_post(me->container, me->menuSignals->sig, 0);
302 return Q_TRAN(&SerialCommand_operational);
304 /* CR/LF so signal the command to the container class */
305 QActive_post(me->container, msig->sig, me->vargs[0].v.uint32);
306 /* single argument passed in signal, flag vargs available */
309 return Q_TRAN(&SerialCommand_operational);
312 /* any char not accepted as a digit returns non-zero */
313 if (serialProcessDigit(me, ch)) {
\r
314 serialClearVargs(me);
315 return Q_TRAN(&SerialCommand_failTermination);
317 /* Successfully accumulated digit */
321 return Q_SUPER(&SerialCommand_operational);
323 /*..........................................................................*/
324 static QState SerialCommand_awaitSeparator(SerialCommand *me) {
328 /* unpack the two bit VARG type; one of four in menuSignal byte */
329 type = (me->menuSignals + (me->parser->idx & ~EOC))->varg;
330 type >>= me->vidx << 1;
332 serialInitialiseVarg(me, type);
335 case SERIAL_RX_DATA_SIG : {
336 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
338 /* this will re-initialise the varg on entry without issue */
339 return Q_TRAN(&SerialCommand_startArgument);
341 /* remaining handling is the same as SerialCommand_startArgument */
342 if ((ch == '\r') || (ch == '\n')) {
343 /* unexpected early termination, send 0th menuSignal (fail) */
344 QActive_post(me->container, me->menuSignals->sig, 0);
345 return Q_TRAN(&SerialCommand_operational);
348 if ((ch == ' ') || (ch == '\t')) {
354 return Q_TRAN(&SerialCommand_firstArgumentDigit);
357 /* negate on - prefix */
358 me->vargs[me->vidx].type |= VARG_NEGATIVE;
359 return Q_TRAN(&SerialCommand_firstArgumentDigit);
361 if ((ch == 'x') || (ch == 'X')) {
362 /* x or X prefix a hex number */
363 me->vargs[me->vidx].type |= VARG_HEX;
364 return Q_TRAN(&SerialCommand_firstArgumentDigit);
366 /* failure to consume char as digit returns non-zero */
367 if (serialProcessDigit(me, ch)) {
\r
368 serialClearVargs(me);
369 return Q_TRAN(&SerialCommand_failTermination);
371 /* successfully accumulated first number */
372 return Q_TRAN(&SerialCommand_processArgumentDigits);
375 return Q_SUPER(&SerialCommand_operational);
377 /*..........................................................................*/
378 static QState SerialCommand_awaitTermination(SerialCommand *me) {
380 case SERIAL_RX_DATA_SIG : {
381 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
382 if ((ch == '\r') || (ch == '\n')) {
\r
383 MenuSignal *msig = me->menuSignals;
384 msig += me->parser->idx & ~EOC;
\r
385 QActive_post(me->container, msig->sig, me->vargs[0].v.uint32);
386 return Q_TRAN(&SerialCommand_operational);
389 if ((ch == ' ') || (ch == '\t')) {
393 /* any other chars are invalid */
\r
394 serialClearVargs(me);
395 return Q_TRAN(SerialCommand_failTermination);
398 return Q_SUPER(&SerialCommand_operational);
400 /*..........................................................................*/
401 static QState SerialCommand_failTermination(SerialCommand *me) {
403 case SERIAL_RX_DATA_SIG : {
404 char ch = ((SerialParam *)(&Q_PAR(me)))->msg.ch;
405 if ((ch == '\r') || (ch == '\n')) {
406 /* termination, send 0th menuSignal (fail) */
407 QActive_post(me->container, me->menuSignals->sig, 0);
408 return Q_TRAN(&SerialCommand_operational);
412 return Q_SUPER(&SerialCommand_operational);
414 /* Local services ..........................................................*/
415 void serialClearVargs(SerialCommand *me) {
416 VArg *varg = me->vargs;
417 varg->type = 0; varg++;
418 varg->type = 0; varg++;
419 varg->type = 0; varg++;
423 /*..........................................................................*/
424 void serialInitialiseVarg(SerialCommand *me, uint8_t type) {
425 VArg *varg = &me->vargs[me->vidx];
443 /*..........................................................................*/
444 uint8_t serialProcessDigit(SerialCommand *me, uint8_t ch) {
445 VArg *varg = &me->vargs[me->vidx];
446 if ((ch >= '0') && (ch <= '9')) {
449 else if ( (ch == '.')
450 && ((varg->type & VARG_TYPE) == VARG_DBL)
451 && (me->vdiv == 0.0)) {
452 /* start of fractional part */
456 /* if varg is not a hex then any other char is not a number */
457 else if (!(varg->type & VARG_HEX)) {
460 else if ((ch >= 'a') && (ch <= 'f')) {
463 else if ((ch >= 'A') && (ch <= 'F')) {
467 /* didn't find a hex number either */
470 /* handle number based on varg type */
471 switch (varg->type & VARG_TYPE) {
473 if (varg->type & VARG_HEX) {
474 /* range test and return fail on overflow */
475 if (varg->v.uint32 & 0xf0000000) {
478 /* scale accumulator for hex */
479 varg->v.uint32 <<= 4;
481 /* range test and return on overflow */
482 if (varg->v.uint32 > 429496729) {
485 /* range test and return on overflow */
486 if ((varg->v.uint32 == 429496729) && (ch > 5)) {
489 /* scale accumulator */
490 varg->v.uint32 *= 10;
492 /* accumulate the character now converted to number */
493 varg->v.uint32 += ch;
496 /* int32 is accumulated negatively to achieve -2147483648 */
498 /* range test and return on overflow */
499 if (varg->v.int32 < -214748364) {
502 if (varg->v.int32 == -214748364) {
503 if (varg->type & VARG_NEGATIVE) {
504 /* underflow of -2147483648 */
508 /* overflow of 2147483647 */
514 /* scale accumulator */
516 /* accumulate the character now converted to number */
521 if (me->vdiv == 0.0) {
522 /* accumulate matissa */
524 varg->v.dbl += (double)ch;
527 /* accumulate fraction */
528 varg->v.dbl += (double)(ch) / me->vdiv;
535 /*..........................................................................*/
536 void serialFinishVarg(SerialCommand *me) {
537 VArg *varg = &me->vargs[me->vidx];
538 switch (varg->type & VARG_TYPE) {
539 /* int32 was accumulated negatively to allow for -2147483648 */
541 if (!(varg->type & VARG_NEGATIVE)) {
542 /* negate the accumulator to get all positive int32 values */
543 varg->v.int32 = -varg->v.int32;
548 if (varg->type & VARG_NEGATIVE) {
557 /* clean away the hex and negative type flags */
558 varg->type &= VARG_TYPE;
560 /* Container Services ......................................................*/
561 void serialDispatch(SerialCommand *me, QSignal sig, QParam par) {
564 QHsm_dispatch((QHsm *)me);
566 /*..........................................................................*/
567 void serialReleaseVargs(SerialCommand *me) {
570 /*..........................................................................*/
571 void serialDumpArgs(SerialCommand *me) {
573 for (idx = 0; idx < VARG_COUNT; idx++) {
574 switch (me->vargs[idx].type) {
576 serialStr(me, "None: ");
580 serialStr(me, "UInt: ");
581 serialUnum(me, me->vargs[idx].v.uint32);
586 serialStr(me, "Int: ");
587 serialNum(me, me->vargs[idx].v.int32);
592 serialStr(me, "Dbl: ");
593 serialDbl(me, me->vargs[idx].v.dbl, 6);
599 serialStr(me, "\r\n");
601 /*..........................................................................*/
602 void serialChar(SerialCommand *me, char c) {
605 /* tx buffer is empty, try to transmit */
606 if (me->txTail == me->txHead) {
607 /* if SERIAL_BUSY returned, queue the character in the buffer */
608 if ((*me->serialTransmit)(c)) {
609 *(me->txBuffer + me->txHead) = c;
611 if (me->txHead >= me->txSize) {
617 /* queue the character if possible */
618 length = me->txTail - me->txHead;
620 length += me->txSize;
624 return; /* buffer is full, an overrun ~ char was added previously */
627 if (*(me->txBuffer + me->txHead) == '~') {
628 return; /* don't keep stashing more ~ overruns */
630 c = '~'; /* overrun so advise in last character of buffer */
634 /* queue either the character or an overrun ~ character */
635 *(me->txBuffer + me->txHead) = c;
637 if (me->txHead >= me->txSize) {
641 /*..........................................................................*/
642 void serialStr(SerialCommand *me, char *s) {
644 serialChar(me, *s++);
647 /*..........................................................................*/
648 void serialUnum(SerialCommand *me, uint32_t v) {
658 b[i] = '0' + (v % 10);
664 serialChar(me, b[i]);
667 /*..........................................................................*/
668 void serialNum(SerialCommand *me, int32_t v) {
676 /*..........................................................................*/
677 void serialDbl(SerialCommand *me, double number, uint8_t digits) {
678 /* Adapted from Arduino Print.cpp, Print::printFloat */
680 // Handle negative numbers
687 // Round correctly so that print(1.999, 2) prints as "2.00"
688 double rounding = 0.5;
689 for (uint8_t i=0; i<digits; ++i)
694 // Extract the integer part of the number and print it
695 unsigned long int_part = (unsigned long)number;
696 double remainder = number - (double)int_part;
697 serialUnum(me, int_part);
699 // Print the decimal point, but only if there are digits beyond
703 // Extract digits from the remainder one at a time
708 toPrint = (int)(remainder);
709 serialChar(me, toPrint + '0');
710 remainder -= toPrint;
713 /*..........................................................................*/
714 void serialNACK(SerialCommand *me, uint8_t unused) {
\r
715 serialStr(me, "N\r\n");
717 /*..........................................................................*/
718 void serialACK(SerialCommand *me, uint8_t only) {
720 serialStr(me, "A\r\n");
722 serialStr(me, "A"); /* and finish response string in ui function */
725 /*..........................................................................*/