Skip to content

Instantly share code, notes, and snippets.

@BlockoS
Created December 12, 2012 21:41
Show Gist options
  • Save BlockoS/4271886 to your computer and use it in GitHub Desktop.
Save BlockoS/4271886 to your computer and use it in GitHub Desktop.
A simple serial server for Arduino. It handles some special keys.
const char g_welcomeMsg[] = "Welcome!";
#define CH_NUL 0x00
#define CH_BACKSPACE 0x08
#define CH_CANCEL 0x18
#define CH_SUBSTITUTE 0x1a
#define CH_ESCAPE 0x1b
#define CH_SPACE 0x20
#define CH_CSI 0x9b
#define CH_ST 0x9c
static const uint8_t ESC_UP = 0x41;
static const uint8_t ESC_DOWN = 0x42;
static const uint8_t ESC_RIGHT = 0x43;
static const uint8_t ESC_LEFT = 0x44;
static const uint8_t ESC_PGUP = 0x35;
static const uint8_t ESC_PGDOWN = 0x36;
enum ANSI_INPUT_STATE
{
ANSI_NORMAL,
ANSI_ESCAPE,
ANSI_ESCAPE_SEQUENCE,
ANSI_CSI,
ANSI_STRING,
ANSI_TERMINATOR
};
ANSI_INPUT_STATE g_inputState = ANSI_NORMAL;
#define MAX_STRING_BUFFER_SIZE 256
char g_stringBuffer[MAX_STRING_BUFFER_SIZE];
int g_stringBufferLen = 0;
volatile bool g_stringBufferReady = false;
#define MAX_ESCAPE_BUFFER_SIZE 64
char g_escapeBuffer[MAX_ESCAPE_BUFFER_SIZE];
int g_escapeBufferLast = 0;
#define MAX_ANSI_BUFFER_SIZE 128
char g_ANSIStringBuffer[MAX_ANSI_BUFFER_SIZE];
int g_ANSIStringBufferLen = 0;
bool g_ANSIStringReceived = false;
enum SERIAL_READ_STATE
{
SERIAL_READ_NONE = 0,
SERIAL_READ_STANDARD,
SERIAL_READ_ESCAPE
};
enum COMMAND
{
CMD_UP,
CMD_DOWN,
CMD_RIGHT,
CMD_LEFT,
CMD_PGUP,
CMD_PGDOWN
};
#define MAX_COMMAND_STACK_SIZE 32
volatile uint8_t g_commandStackTop = 0;
uint8_t g_commandStack[MAX_COMMAND_STACK_SIZE];
void processEscape()
{
if(g_commandStackTop >= MAX_COMMAND_STACK_SIZE)
{
return;
}
if(g_escapeBufferLast >= 2)
{
if((g_escapeBuffer[0] == 0x1B) && (g_escapeBuffer[1] == 0x5B))
{
if(g_escapeBufferLast == 2)
{
switch(g_escapeBuffer[2])
{
case ESC_UP:
g_commandStack[g_commandStackTop++] = CMD_UP;
break;
case ESC_DOWN:
g_commandStack[g_commandStackTop++] = CMD_DOWN;
break;
case ESC_RIGHT:
g_commandStack[g_commandStackTop++] = CMD_RIGHT;
break;
case ESC_LEFT:
g_commandStack[g_commandStackTop++] = CMD_LEFT;
break;
}
}
else if((g_escapeBufferLast == 3) && (g_escapeBuffer[3] == 0x7E))
{
switch(g_escapeBuffer[2])
{
case ESC_PGUP:
g_commandStack[g_commandStackTop++] = CMD_PGUP;
break;
case ESC_PGDOWN:
g_commandStack[g_commandStackTop++] = CMD_PGDOWN;
break;
}
}
}
}
g_escapeBufferLast = 0;
}
// Shameless rip of http://www.mpp.mpg.de/~huber/vmssig/src/C/RMESCSEQ.C
char ANSIDecode(char data)
{
if(g_inputState == ANSI_NORMAL)
{
if(data == CH_ESCAPE)
{
// A new escape sequence is starting.
g_inputState = ANSI_ESCAPE;
g_escapeBuffer[0] = data;
g_escapeBufferLast = 0;
}
else if(data == CH_CSI)
{
// The escape sequence was restarted.
g_inputState = ANSI_CSI;
// Save sequence in case it has to be replayed.
g_escapeBuffer[0] = data;
g_escapeBuffer[1] = '[';
g_escapeBufferLast = 1;
}
else
{
// This is a "standard" input.
return data;
}
return -1;
}
/* Escape sequence. */
if((data < CH_SPACE) || (data == CH_CSI))
{
switch(data)
{
case CH_CANCEL:
case CH_SUBSTITUTE:
// Cancel escape sequence and go back to normal.
g_inputState = ANSI_NORMAL;
g_ANSIStringReceived = false;
g_ANSIStringBuffer[0] = 0;
g_ANSIStringBufferLen = 0;
break;
case CH_BACKSPACE:
// Erase previous characters.
if((g_inputState == ANSI_CSI) && (g_escapeBufferLast == 1))
{
g_inputState = ANSI_ESCAPE;
g_escapeBufferLast = 0;
}
else if((g_inputState == ANSI_ESCAPE_SEQUENCE) && (g_escapeBufferLast == 1))
{
g_inputState = ANSI_ESCAPE;
g_escapeBufferLast = 0;
}
else if((g_inputState == ANSI_ESCAPE) && (g_escapeBufferLast == 0))
{
g_inputState = ANSI_NORMAL;
}
else if(g_inputState == ANSI_TERMINATOR)
{
g_inputState = ANSI_STRING;
}
else if(g_inputState == ANSI_STRING)
{
if(g_ANSIStringBufferLen > 0)
{
--g_ANSIStringBufferLen;
}
else
{
g_inputState = ANSI_ESCAPE;
}
}
else if(g_escapeBufferLast > 0)
{
--g_escapeBufferLast;
}
break;
case CH_ESCAPE:
if(g_inputState == ANSI_STRING)
{
g_inputState = ANSI_TERMINATOR;
}
else
{
g_inputState = ANSI_ESCAPE;
g_escapeBuffer[0] = CH_ESCAPE;
g_escapeBufferLast = 0;
}
break;
case CH_CSI:
g_inputState = ANSI_CSI;
g_escapeBuffer[0] = CH_CSI;
g_escapeBuffer[1] = '[';
g_escapeBufferLast = 1;
break;
default:
if(data != CH_NUL)
{
return data;
}
break;
}
return -1;
}
// Put bytes into the escape sequence buffer
if((g_inputState != ANSI_STRING) && (g_inputState != ANSI_TERMINATOR))
{
if(g_escapeBufferLast < MAX_ESCAPE_BUFFER_SIZE)
{
g_escapeBuffer[++g_escapeBufferLast] = data;
}
}
switch(g_inputState)
{
case ANSI_ESCAPE:
switch(data)
{
case '[':
g_inputState = ANSI_CSI;
break;
case '_':
case 'P':
case 'Q':
case 'R':
case 'X':
case '^':
case ']':
g_inputState = ANSI_STRING;
g_ANSIStringReceived = true;
g_ANSIStringBufferLen = 0;
break;
default:
if((data > 57) && (data < 177))
{
g_inputState = ANSI_NORMAL;
return -2;
}
else
{
g_inputState = ANSI_ESCAPE_SEQUENCE;
}
};
break;
case ANSI_ESCAPE_SEQUENCE:
if((data > 57) && (data < 177))
{
g_inputState = ANSI_NORMAL;
return -2;
}
case ANSI_CSI:
if((data > 64) && (data < 177))
{
g_inputState = ANSI_NORMAL;
return -2;
}
case ANSI_STRING:
if(data == CH_ESCAPE)
{
g_inputState = ANSI_TERMINATOR;
}
else if(data == CH_ST)
{
g_inputState = ANSI_NORMAL;
g_ANSIStringReceived = false;
}
else if(g_ANSIStringReceived)
{
if(g_ANSIStringBufferLen < MAX_ANSI_BUFFER_SIZE)
{
g_ANSIStringBuffer[g_ANSIStringBufferLen++] = data;
}
else
{
g_ANSIStringReceived = false;
g_ANSIStringBufferLen = 0;
g_inputState = ANSI_NORMAL;
}
}
break;
case ANSI_TERMINATOR:
if(data == '\\')
{
g_inputState = ANSI_NORMAL;
g_ANSIStringReceived = false;
}
else
{
if(data >= CH_SPACE)
{
g_inputState = ANSI_STRING;
}
if(g_ANSIStringReceived && ((g_ANSIStringBufferLen+1) < MAX_ANSI_BUFFER_SIZE))
{
g_ANSIStringBuffer[g_ANSIStringBufferLen++] = CH_ESCAPE;
g_ANSIStringBuffer[g_ANSIStringBufferLen++] = data;
}
}
}
return -1;
}
void processInput(char data)
{
if(data == 127)
{
if(g_stringBufferLen > 0)
{
Serial.print(data);
g_stringBuffer[--g_stringBufferLen] = '\0';
}
}
else
{
if((data == '\r') || (data == '\n'))
{
data = '\0';
Serial.println();
g_stringBufferReady = true;
}
else
{
Serial.print(data);
}
if(g_stringBufferLen < MAX_STRING_BUFFER_SIZE)
{
g_stringBuffer[g_stringBufferLen++] = data;
}
}
}
void setup()
{
g_stringBufferReady = false;
g_stringBufferLen = 0;
Serial.begin(115200);
Serial.println(g_welcomeMsg);
Serial.print('>');
}
void serialEvent()
{
while(Serial.available())
{
char data = Serial.read();
char ret;
if((data > 127) && (data < 160) && (data != CH_CSI))
{
ret = ANSIDecode(CH_ESCAPE);
if(ret >= 0)
{
data = ret;
processInput(data);
}
else if(ret == -2)
{
processEscape();
}
data = (data & 0x7f) | 0x40;
}
ret = ANSIDecode(data);
if(ret >= 0)
{
processInput(data);
}
else if(ret == -2)
{
processEscape();
}
}
}
void loop()
{
for(uint8_t i=0; i<g_commandStackTop; ++i)
{
switch(g_commandStack[i])
{
case CMD_UP:
// todo
break;
case CMD_DOWN:
// todo
break;
case CMD_RIGHT:
// todo
break;
case CMD_LEFT:
// todo
break;
case CMD_PGUP:
// todo
break;
case CMD_PGDOWN:
// todo
break;
}
}
g_commandStackTop = 0;
if(g_stringBufferReady)
{
uint8_t j=0;
for(uint8_t i=0; i<g_stringBufferLen; ++i)
{
if(g_stringBuffer[i] == '\0')
{
if((i-j) >= 1)
{
Serial.print("String:");
Serial.println(g_stringBuffer+j);
// todo
}
j = i+1;
}
}
g_stringBufferLen = 0;
Serial.print('>');
g_stringBufferReady = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment