Sdfrethsh

 

This page's comments/discussion on Arduino Forum: Sdfrethsh (Arduino shell) discussion

 

Introduction

This is yet another shell for the Arduino, based on fruitshell.

 

Edit: there is now version 1.1.0 on svn (see below), which can embed one of three unofficial DHCP libraries for Arduino: Arduino DHCP Library: Version 0.4 (jordanterrell); ASocket library (google code) (kegger); ArduinoEthernet (gkaindl). For more about the library installation procedure, see the dhcp test sketch ardtestdhcp.pde. Note that with (gkaindl), compiled Binary sketch size is: 14918 bytes.

 

I recently got into both ArduinoEthernetShield and emulino: arduino cpu emulator (also here), so I thought it would be nice to have a shell as a test program that could be ran either via usb-serial or ethernet; and which could be emulated with emulino.

There seem to be plenty of Arduino shells out there:

fruitshell (fruitshell forum post) was taken as a start, as it is relatively simple - however, it had problems being simulated with emulino. So the shell engine was rewritten, and then the command parser was copied from fruitshell, communication made switchable between ethernel and serial, and few functions added ... (and so, the sdfrethsh would stand for sd's 'fruit' ethernet shell :) ).

The shell functions in 'character mode' (that is, with characters, not buffered lines) - so the access tools should be appropriately set.

The code can be found on SourceForge.net Repository - (sdaaubckp) Index of /sdfrethsh, and v.1.0.0 is also included below for reference. sdfruiteth.pde builds to around 8K, and can be put on either ATmega168 or ATmega328 (most of development was done on emulino, tested with Duemillanove w/ATmega328 w/ Ethernet Shield).

 

Serial vs. Ethernet

  • To use ethernet, you will need the Ethernet shield attached, and you need to build the .pde with the "#define ETHERNET" statement in the code uncommented, before you upload it to your Arduino board.
  • No Etherned shield is needed for serial mode (although ethernet libraries could still be included). You need to build the .pde with the "//#define ETHERNET" statement in the code commented, before you upload it to your Arduino board.
    • Note that the shell can only be emulated in emulino, when it is built in serial mode

 

Note that the instructions below refer to the process in respect to Ubuntu Linux as PC OS.

 

 

Running - emulator

Build the sdfruiteth.pde in serial mode. Then, note that during a "Verify" the .hex file is written to /tmp, find the built hex file, and run

 
stty -icanon ; ./emulino /tmp/buildXXXXXXXXXXXXXXXX.tmp/sdfruiteth.cpp.hex

A shell prompt should be displayed - click TAB for help, Backspace to cancel an entry. Below is an example session:

  
$ stty -icanon 115200 ; ./emulino /tmp/build3918414324196097168.tmp/sdfruiteth.cpp.hex
emulino: Loading hex image: /tmp/build3918414324196097168.tmp/sdfruiteth.cpp.hex
sdfrethsh # read 1
> read 1		/1530	 (6)
Pin 1: 0

sdfrethsh # output 5
> output 5		/1819	 (8)
Output Pin 5

sdfrethsh # read 5
> read 5		/2142	 (6)
Pin 5: 0

sdfrethsh # write 1 5
> write 1 5		/2340	 (9)
pin 21 1
pins 00000000 00000000 00000100
Writen to Pin 5: 1

sdfrethsh # read 5
> read 5		/2558	 (6)
Pin 5: 1

sdfrethsh # exit
> exit		/2664	 (4)
cycles: 555078429

$

Note that the exit command should not "truly" exit if either ethernet mode, or echo mode (as likely for a real serial connection) is active (if by chance a real board "truly" exits, it needs to be rebooted to have the program start again).

 

Running - serial via USB

Build the sdfruiteth.pde in serial mode. Then, upload to your Arduino board, and in terminal, execute:

 
screen /dev/ttyUSB0 115200

The shell will most likely not be displayed at first, so click Del or Tab to force the prompt to display.

  • Also, it is likely that your keypresses will not be echoed back, so activate echo mode by pressing ^ (Shift+6 usually).
  • Once this is done, the above shell session should be repeatable in identical manner.
  • Serial communication on the Arduino side is set to start at 115200 bps, modify to your liking.
  • Note that disconnecting the USB cable will terminate screen

 

Running - ethernet

Modify the IP settings to suit your needs, and build the sdfruiteth.pde in ethernet mode. Then, upload to your Arduino board - and in terminal, first see if ping responds:

 
$ ping 192.168.1.123
PING 192.168.1.123 (192.168.1.123) 56(84) bytes of data.
64 bytes from 192.168.1.123: icmp_seq=1 ttl=128 time=6.17 ms
64 bytes from 192.168.1.123: icmp_seq=2 ttl=128 time=2.18 ms
^C

Then, try to establish a telnet connection to the board:

 
$ telnet 192.168.1.123 23
Trying 192.168.1.123...
Connected to 192.168.1.123.
Escape character is '^]'.
^]

telnet> mode character
^?
sdfrethsh #
Echo: ON
sdfrethsh # hello
> hello 	/5790	 (6)
Version 1.0.0


Note that as soon as connection is obtained, we should put telnet in "character mode", so the shell responds the same as via emulator or serial access. So, in the above telnet connection:

  • As soon as connection is obtained, ^] (Ctrl+]) and Enter is pressed
  • The telnet prompt appears; here "mode character" (and Enter) is typed
  • There is no apparent response, but we should indeed be in character mode now: if Backspace is pressed, the prompt should be displayed.
  • Also, it is likely that your keypresses will not be echoed back, so activate echo mode by pressing ^ (Shift+6 usually); test if typing echoing is ok by typing 'hello' and Enter.
  • To exit the telnet session, press (Ctrl+]) and Enter - and at the telnet prompt, type q and Enter.

 

Code - sdfruiteth.pde

 
/*
 *  sdfruiteth.pde
 *  Shell-like environment for Arduino,
 *  that can be simulated in emulino.
 *  Based on minifruit (Fruit One) by Halid Ziya Yerebakan.
 *  This one should be working with Ethernet.
 *
 *  The code reports its results through serial or ethernet
 *
 *  For sim, build in serial mode, and:
 *  stty -icanon 115200 ; ./emulino /path/to/sdfruiteth.cpp.hex
 *  in order to get sim of tab, del and echo keypresses
 *
 *  sdfruiteth.pde
 *  ------------
 *  Latest update: May 07 2010
 *  ------------
 *  Copyleft: use as you like
 *  sd [] imi.aau.dk
 *
 */


// Commands

byte shellstate = 3; //start from show prompt state
/*
state = 0 : prompt has been shown, no in data yet
state = 1 : first incoming character came in,  data still incoming
state = 2 : EOL has been reach, incoming data completed
state = 3 : data has been processed, ready to show prompt.
*/
int timestep=0;
#define MAXCHARS 100
char line[MAXCHARS];
int linept=0;
char rch;
#define LF 10 //LineFeed
#define CR 13 //CarrRet
#define TAB 9
#define DEL 127
#define ECHR '^' //echo char
byte echomode=0;

// from minifruit shell - commands
#define READ 1
#define WRITE 2
#define OUT 3
#define IN 4
#define DELAY 5
#define PULSE 6
#define EXIT 7
#define VERSION 8


/*
* COMM FUNCTIONS  ****************************************************************
*/

// keep ETHERNET commented (undefined) for USB/serial mode build
//#define ETHERNET

/*
* in ETHERNET case, the shell should be
* Server class - listening for
* incoming connections
*/

/*
* Note for ethernet:
* calling as:
*
*   stty -icanon ; telnet 192.168.1.77 23
*
* once connected, escape the telnet with ^] (Ctrl+]) and to into character mode:
*
*   ^]
*   telnet> mode character
*
* press enter - no message will be printed, but one is now in character mode.
* Then press Tab to show the help screen of the shell.
* No characters will be echoed, so you may want activate echo mode;
*   unfortunately '^' may get garbled by telnet, but you can copypaste it from
*   gnome-terminal from the help text shown on tab...
*
*/


// commmode
#ifdef ETHERNET
static byte commmode = 1; // ethernet
#else
static byte commmode = 0; // serial
#endif

#include <Ethernet.h>

// network configuration.  gateway and subnet are optional.
// Ethernet shield should be pingable at the address specified below,
//   its leds should blink at ping too
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 123 };
byte gateway[] = { 192, 168, 1, 1 };
byte subnet[] = { 255, 255, 255, 0 };

// telnet defaults to port 23
Server server = Server(23);


void commBegin()
{
#ifdef ETHERNET
  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);
  // start listening for clients
  server.begin();
#else
  Serial.begin(115200);
#endif
}


int commAvailable()
{
#ifdef ETHERNET
  return server.available();
#else
  return Serial.available();
#endif
}



byte commRead()
{
#ifdef ETHERNET
  //the client read seems to block until enter is received
  // use telnet's character mode to get char by char response
  Client tclient = server.available();
  return tclient.read();
#else
  // serial will block in emulation until enter is received, unless
  // stty -icanon is ran before emulating..
  return Serial.read();
#endif
}

// Variadic macros are C99-only
#ifdef ETHERNET
#define commPrint(...)  do { server.print(__VA_ARGS__); } while (0)
#define commPrintln(...)  do { server.println(__VA_ARGS__); } while (0)
#else
#define commPrint(...)  do { Serial.print(__VA_ARGS__); } while (0)
#define commPrintln(...)  do { Serial.println(__VA_ARGS__); } while (0)
#endif



/*
* MAIN FUNCTIONS  ****************************************************************
*/

void setup()
{
  commBegin(); // initialize the communication
}

void loop()
{
  if (shellstate == 3) {
    printPrompt();
    shellstate = 0;
  }
  checkForInData();
  processInData();
  //commPrintln(shellstate);
  timestep++;
  delay(20);
}






/*
* SHELL FUNCTIONS  ****************************************************************
*/

void printPrompt()
{
  commPrint("sdfrethsh # ");
}


void checkForInData()
{
  while(commAvailable()) // does not block if no data available
  {
    rch = commRead();
    line[linept] = rch;
    linept++;

    if (rch == TAB){
      // usage and reset - for tab
      printUsage();
      shellstate = 3;
      linept = 0;
      return;
    }

    if (rch == DEL){
      // immediate reset - for delete
      commPrintln();
      shellstate = 3;
      linept = 0;
      return;
    }

    if (rch == ECHR){
      // switch echo mode and reset
      commPrintln();
      commPrint("Echo: ");
      if (echomode == 0) {
        echomode = 1;
        commPrintln("ON");
      } else {
        echomode = 0;
        commPrintln("off");
      }
      shellstate = 3;
      linept = 0;
      return;
    }

    if (echomode == 1) commPrint(rch);

    if (shellstate == 0) {
      shellstate = 1;
      //commPrintln();
      //commPrintln("checkForInData ");
    }

    line[linept] = 0;
    if ((rch == CR) || (rch == LF))
    {
      shellstate = 2;
    }
  }
}


void processInData()
{
  if (shellstate == 2)
  {
    if (echomode == 1) commPrintln(); //add newline for echo mode
    linept--; //ignore last EOL
    line[linept] = 0; // and shorten string
    // printout command, timestep and number of chars in command
    commPrint("> ");
    commPrint(line);
    commPrint("\t\t/");
    commPrint(timestep);
    commPrint("\t (");
    commPrint(linept);
    commPrintln(")");

    // parse'n'execute command
    execCommand();

    // add empty line
    commPrintln();

    shellstate = 3;
    linept = 0;
  }
}




// orig: showConsole() in minifruit shell
void execCommand()
{
   //Do command
   int command=parseLine(line);
   int param1,param2,param3;

   switch(command)
   {
   case READ :
       // Parse param as pin number
       param1 = line[5] - '0';
       if (line[6]>='0' && line[6]<='9')
         param1=10*param1+line[6]-'0';

       commPrint("Pin ");
       commPrint(param1);
       commPrint(": ");
       commPrintln(digitalRead(param1));
       break;

   case WRITE  :
     param1 = line[6] - '0'; // Digital value
     param2 = line[8] - '0'; // Line number
       if (line[9]>='0' && line[9]<='9')
         param2=10*param2+line[9]-'0';
       digitalWrite(param2,param1);
       commPrint("Writen to Pin ");
       commPrint(param2);
       commPrint(": ");
       commPrintln(param1);

     break;

    case OUT:
         param1 = line[7] - '0';
         if (line[8]>='0' && line[8]<='9')
         param1=10*param1+line[8]-'0';
         pinMode(param1, OUTPUT);
         commPrint("Output Pin ");
         commPrintln(param1);
    break;

    case IN:
         param1 = line[6] - '0';
         if (line[7]>='0' && line[7]<='9')
         param1=10*param1+line[7]-'0';
         pinMode(param1, OUTPUT);
         commPrint("Input Pin ");
         commPrint(param1);
         break;
     case DELAY:
         param1 = line[6] - '0'; // Digital value
         param2 = line[8] - '0';
         param3 = line[10] - '0'; // Line number
         if (line[11]>='0' && line[11]<='9')
         param3=10*param3+line[11]-'0';
         commPrint("Active pin ");
         commPrint(param3);
         commPrint(" by ");
         commPrint(param1);
         commPrint(" seconds value:");
         commPrintln(param2);
         digitalWrite(param3,param2);
         delay(1000*param1);
         digitalWrite(param3,!param2); // Not of parameter
     break;
     case PULSE:
         param1 = line[6] - '0'; // Voltage Level
         param2 = line[8] - '0'; // Line Number
         if (line[11]>='0' && line[11]<='9')
         param2=10*param2+line[11]-'0';

         commPrint("Wave to pin ");
         commPrint(param2);
         commPrint(" by ");
         commPrint(param1);
         commPrintln(" V");
         analogWrite(param2,param1*50);

       break;
     case VERSION: commPrintln("Version 1.0.0"); break;
     case EXIT: if ((echomode == 0) && (commmode ==0)) exit(0); break; // only useful for sim!!
     default: commPrintln("Unknown command"); break;
   }

}


void printUsage()
{

commPrintln("Console Usage:");
commPrint("        ");
commPrint(ECHR);
commPrintln("                   : Switch echo mode ");
commPrintln("        read <pin>          : Read target pin ");
commPrintln("	write <value> <pin> : Write value to desired pin ");
commPrintln("	output <pin> 	    : Configure pin as output ");
commPrintln("	input  <pin>		: Configure pin as input ");
commPrintln("	delay <second> <value> <pin> : Writes value to pin for given delay ");
commPrintln("	pulse <value> <pin>	: Arrages voltage value to given pin by PWM ");
commPrintln("        exit				: Exit");
commPrintln("        hello				: Version");
}


int stringCompare(char* line1,char* line2,int length)
/*Returns 1 when they are equal*/
{
  int i=0;
  int result=1;
  for(i=0;i<length;i++)
     result = result && (line1[i]==line2[i]);
  return result;
}

int stringLength(char* line)
{
  int i;
  for(i=0;line[i]!=0;i++);
  return i;
}


int parseLine(char* line)
{
  //if (stringCompare(line,"exit",4) || (stringLength(line) < 4 )) return EXIT;
  if (stringCompare(line,"exit",4)) return EXIT;
  if (stringCompare(line,"read",4)) return READ;
  if (stringCompare(line,"write",5)) return WRITE;
  if (stringCompare(line,"output",5)) return OUT;
  if (stringCompare(line,"input",5)) return IN;
  if (stringCompare(line,"delay",5)) return DELAY;
  if (stringCompare(line,"pulse",5)) return PULSE;
  if (stringCompare(line,"hello",5)) return VERSION;
}


This page's comments/discussion on Arduino Forum: Sdfrethsh (Arduino shell) discussion

Share