Terminal

Why?

When I was developing both the plotter and the software that controls it I needed some way to test out simple commands. I started doing this with standard library C function such as scanf() but this wasn't the easiest thing to use as you couldn't navigate across a line as arrow key presses were registered as normal key presses so keys characters were actually printed. Also, there was no way to repeat the last command easily. I am a big fan of the UNIX/bash shell so I decided to try and emulate it. That way I could issue commands quickly, easily and dare I say it enjoy it while I did it.

Look

When opened the terminal looks like this:

empty terminal

It uses the Andale Mono typeface, I chose this because it is monospaced and I could use it as I have the typeface in a TrueType font file.

What it does

Here is a list of what you can do with what keys in the Sybil terminal:

  • ← & →: These allow you to move along the current line.
  • ↑ & ↓: These allow you to move through previous commands.
  • BACKSPACE: delete the preceding character with this key.
  • TAB: These auto completes what is currently written.
  • ENTER: This processes the command.
  • CTRL + c: This clears the screen.
  • CTRL + a: Moves the prompt to the beginning of the line.
  • CTRL + e: Moves the prompt to the end of the line.

Commands & Options

There are several commands you can issue, most of these commands have options and all have arguments, options can be declared with a preceding hyphen. Here is a list of the commands with their arguments and possible options.

  • move [-r] x y
  • line [-r] x0 y0 x1 y1
  • rect [-rc] x y w h
  • circle [-rc] x y r
  • poly [-rc] x y nSides r
  • get pos; this returns the current position of the plotter
  • pen down/up
  • delay ms; this changes the speed of the plotter, by varying the delay between steps.

What happens

When enter is pressed the command goes through several processes. The first is the parse the string and to get useful information out of it. This is done by a handy method called explode, it is called that because it is inspired by PHP's explode, even though it works slightly differently. The method looks like this:

void STerm::explode(string command, char sep, vector &tokens, vector &options) {
	tokens.clear();
	
	
	string t = "";
	for (int i = 0; i < command.length(); i++) {
		if (command[i] != sep) t+= command[i];
		else {
			//don't add things starting with a space..
			//or an empty string...
			if (t[0] != ' ' && t != "") {
				tokens.push_back(t);
				t = "";
			}
		}
	}
	
	// add the final one...
	// but don't add it if it's a space... or nothing
	if (t != " " && t != "") {
		tokens.push_back(t);
	}
	
	//now do the options...
	//any word starting with a '-' is an option
	
	for (int i = 0; i < tokens.size(); i++) {
		if (tokens[i].at(0) == '-') {
			//now start at character 1 and add to options to vector
			for (int j = 1; j < tokens[i].length(); j++) {
				options.push_back(tokens[i].at(j));
			}
			
			//now remove the string as it is an option not a valid token
			tokens.erase(tokens.begin()+i);
			//we need decrement i now as we have just erased...
			i--;
		}
	}

	
}

So now we have two vectors for the options and the tokens we can process it. Next the first token is passed to a function which decides where it's going to, if it can't find anywhere it returns a 'command not found' error. However, if there it is a valid command it gets passed to the method that handles that particular function in the Commander class. So for example the rectangle function looks like this in code:


string SCommand::rect(vector &tokens, vector &options) {

	bool rel = false; //relative flag
	bool cor = false; //corner flag
	
	SPoint currentPos;
		
	//check for correct arguments and options...
	//check for 4 arguments
	if (tokens.size() != 5) {
		return "usage: rect [-rc] x y w h";
	} 
	
	//check for only r and/or c option
	for (int i = 0; i < options.size(); i++) {
		if (options[i] == 'r') {
			rel = true;
			//now find the current pos of the plotter
			currentPos = serialConnection->getPos();
		}
		else if (options[i] == 'c') {
			cor = true;
		}
		else {
			return "rect: illegal option --" + options[i];
		}
	}
			
	//get all the ints...
	int c[4];
	
	for (int i = 1; i < 5; i++) {
		string comment = "";
		//convert
		c[i-1] = stringToInt(tokens[i], comment);
		//if one is bad, then exit loop
		if (comment != "") {
			return "rect: " + comment;
		}
	}
	
		
	//set default variables...
	int x = c[0];
	int y = c[1];
	int w = c[2];
	int h = c[3];
	
	//do relative
	if (rel) {
		x+= currentPos.x;
		y+= currentPos.y;
	}
	
	//do corner
	if (cor) {
		x-= w/2;
		y-= h/2;
	}
	
	if (x < 0 || y < 0 || w < 0 || h < 0) {
		return "invalid arguments: check for negative values";
	}
	
	printf("x = %i, y = %i, w = %i, h = %i; r = %i, c = %i\n", x, y, w, h, rel, cor);
	
	//now make the SPoint vector
	vector points;
	points.push_back(SPoint(PEN_UP_POINT, 0));
	points.push_back(SPoint(x, y));
	points.push_back(SPoint(PEN_DOWN_POINT, 0));
	points.push_back(SPoint(x+w, y));
	points.push_back(SPoint(x+w, y+h));
	points.push_back(SPoint(x, y+h));
	points.push_back(SPoint(x, y));
	
	//add pen up at end
	points.push_back(SPoint(PEN_UP_POINT, 0));
	
	//now send...
	serialConnection->sendMultipleMove(points, false);
	printf("sent multiple points...\n");
	previewPtr->startedDrawing();
			
	return "";
	
}

The string that is returned contains the error message if there is one, that's why it returns an empty string if it gets to the end. And if it gets there everything was ok and the command is sent to the plotter via the Serial class, which handles all the communication.