Simple command parser (Part 2)

Part 1, 2

In the previous version, the command parser is using one single alpha character as an opcode while the argument is computed on the fly using simple arithmetic. In this version, the opcode is extended to (almost) any number of characters and the argument is stored in a vector which is then converted into integers or floats using the iota and itof functions. Please note that the use of itof leads to the same limitations than in the previous version. Also, because the opcode has no more fixed length, a separator must be used in between the opcode and the argument. The example given below makes use of either the ‘:’ character or a space character.

Next is a new set of opcodes. They are more explicit however they require more typing efforts.

const char OPC_NONE[] = "NONE";
const char OPC_BLINKS[] = "BLK";
const char OPC_ACTIVATED[] = "ACT";
const char OPC_ON_RATIO[] = "ONR";
const char OPC_PERIOD[] = "PER";
const char OPC_LST_PARAM[] = "LST";
const char OPC_RST_PARAM[] = "RST";
const char OPC_SAV_PARAM[] = "SAV";
const char OPC_ERROR[] = "ERR";

And here is parsing code

void ParseUartData(void)
{
	if (Serial.available() > 0) {
		/* As long as data is available from the com port */
		while (Serial.available() > 0) {
			/* Set variables to their default values */
			bool delimiterDetected = false;
			char vOpcode[8];
			uint8_t opcodeChars = 0;
			vOpcode[0] = '\0';
			char vArgument[12];
			uint8_t argumentChars = 0;
			/* While cr or nl characters are not met */
			while (true) {
				/* Read next char */
				char dataIn = Serial.read();
				/* Interpret character */
				if ((dataIn == 13) || (dataIn == 10)) {
					break;
				} else  if ((dataIn == ':') || (dataIn == ' ')) {
					/* Delimiter */
					delimiterDetected = true;
				} else if ((dataIn >= 'A') && (dataIn <= 'Z')) {
					/* Parse opcode */
					if (!delimiterDetected) {
						vOpcode[opcodeChars] = dataIn;
						opcodeChars += 1;
					} else {
						vOpcode[0] = '\0';
						break;						
					}
				} else if (((dataIn >= '0') && (dataIn <= '9')) || (dataIn == '.')) {
					/* Parse argument */
					if (delimiterDetected) {
						vArgument[argumentChars] = dataIn;
						argumentChars += 1;
					} else {
						vOpcode[0] = '\0';
						break;						
					}
				} else {
					/* Unexpected character */
					vOpcode[0] = '\0';
					break;					
				}
				delay(1);
			}
			/* Append termination characters */
			vArgument[argumentChars] = '\0';
			vOpcode[opcodeChars] = '\0';
			/* Convert vector */
			/* 
			If the vector contains a decimal separator, only the integer part 
			of the floating point number is returned
			*/
			int32_t intArgument = atol(vArgument);
			/* The vector may not contain a decimal separator */
			float fltArgument = atof(vArgument);
			/* Parse opcodes */
			if (vOpcode[0] != '\0') {
				/* Uncomment for Debug */
				// Serial.print("opcode: ");
				// Serial.print(vOpcode);
				// Serial.print(", argument: ");
				// Serial.print(intArgument);
				// Serial.print(" (");
				// Serial.print(fltArgument, 6);
				// Serial.print(")");
				// Serial.println();	
				if (strcmp(OPC_ACTIVATED, vOpcode) == 0) {
					Serial.print("Turn led ");
					if (intArgument == 1) {
						*(_ledPort) |= _ledPinMask; 
						Serial.println("on");
					} else if (intArgument == 0) {
						*(_ledPort) &= ~_ledPinMask; 					
						Serial.println("off");
					}
					_blinks = 0;		
				} else if (strcmp(OPC_BLINKS, vOpcode) == 0) {
					Serial.print("Blinks = ");
					Serial.println(intArgument);
					_blinks = intArgument;
					/* Reset blink counter */
					_blinkCounter = 0;
				} else if (strcmp(OPC_ON_RATIO, vOpcode) == 0) {
					Serial.print("On/off ratio = ");
					Serial.println(fltArgument, 2);
					_onRatio = fltArgument;	
				} else if (strcmp(OPC_PERIOD, vOpcode) == 0) {
					Serial.print("Period = ");
					Serial.println(intArgument);
					_period = intArgument;
				} else if (strcmp(OPC_LST_PARAM, vOpcode) == 0) {
					PrintParameters();
				} else if (strcmp(OPC_RST_PARAM, vOpcode) == 0) {
					Serial.print("Reading parameters... ");
					GetDefaultParameters(bool(intArgument));
					Serial.println("done");
				} else if (strcmp(OPC_SAV_PARAM, vOpcode) == 0) {
					Serial.print("Saving parameters... ");
					SetDefaultParameters();
					Serial.println("done");
				} else if (strcmp(OPC_ERROR, vOpcode) == 0) {
					/* Report error */
				}
				/* Update subsidiary variables */
				_onDuration = (_period * _onRatio);
				_offDuration = (_period - _onDuration);
			}
		}
	}
}

You get the point now: variable length opcodes allow an almost unlimited choice of explicit commands which might be very useful in complex applications.

However, and once again, the aim of this series of post is to provide you with a simple yet powerful starting point with command parsing. Advanced application may require much more rugged transactions, including checksum or CRC (cyclic redundancy check) and data descriptors for example.

HTH

Leave a Reply

You must be logged in to post a comment.