 }
 
 static void writeText(String s) {
-  // overwrite write text
-  text = s;
-  text.toUpperCase();
-
-  // reset current state
-  bot.resetPosition();
-}
+  if (s.length() > 0) {
+    char c = s[0];  // grab the next character to write
+    
+    // put the rest of the command back at the beginning of the queue
+    cmdQ.insert(s.substring(1), LOGO_CMD_WT);
+
+    // use the LogobotText library to write the letter
+    LogobotText::writeChar(c, bot.state.x, bot.state.y);  
+  } 
+}

            break;
        case LOGO_CMD_SE:
            bot.emergencyStop();
			text = "";
            break;
		case LOGO_CMD_CS:
			bot.resetPosition();

  // keep the bot moving (this triggers the stepper motors to move, so needs to be called frequently, i.e. >1KHz)
  bot.run();

  // dequeue commands from textQ, then cmdQ
  // textQ will be emptied before processing anything in cmdQ
  if (dequeueFrom(textQ))
    dequeueFrom(cmdQ);
}

static boolean dequeueFrom(CommandQueue q) {
  if (!q.isEmpty()) {
    // see if we can queue the next command to the bot...  two situations where we can dequeue:
    //   1) if the next command is a movement command, and the bots motion queue isn't full
    //   2) the bot has finished whatever it was doing
    if ( 
        ( q.peekAtType() <= LOGO_MOVE_CMDS && !bot.isQFull() )
        ||
        !bot.isBusy()
    ) {
        // dequeue and process next command - ultimately passing it over to the bot object for execution
        doLogoCommand(q.dequeue());
    }
  }
  // return true when this queue is empty
  return q.isEmpty();
}

static void handleCollision(byte collisionData)
