Support library for the ESP8266 Wireless Terminal. Can also be used for communicating with any VT100-compatible terminal.

espterm.cpp

Committer:
MightyPork
Date:
2017-03-19
Revision:
4:294e8f53ebcd
Parent:
3:1114012184bf
Child:
5:7379bd37f3e2

File content as of revision 4:294e8f53ebcd:

#include "mbed.h"
#include "espterm.hpp"

#include <cstdarg>


void ESPTerm::init(Serial *s)
{
    cs = -1;
    device_ok = false;
    
    on_mouse_click = NULL;
    on_button = NULL;
    on_esp_reset = NULL;
    on_char_rx = NULL;
    on_key_press = NULL;
    on_osc_rx = NULL;
    
    ser = s;
    ser->attach(callback(this, &ESPTerm::ser_rx_char), Serial::RxIrq);
}


ESPTerm::ESPTerm(Serial *s)
{
    init(s);    
}


ESPTerm::ESPTerm(void)
{
    // Defaults
    init(new Serial(PA_2, PA_3, 115200));
}


ESPTerm::ESPTerm(PinName txPin, PinName rxPin, int baud)
{
    init(new Serial(txPin, rxPin, baud));
}


// ----- Printing -----


int ESPTerm::printf(const char *format, ...)
{    
    std::va_list arg;
    va_start(arg, format);
    int r = ser->vprintf(format, arg);
    va_end(arg);
    
    return r;
}


// alias of printf
int ESPTerm::print(const char *format, ...)
{    
    std::va_list arg;
    va_start(arg, format);
    int r = ser->vprintf(format, arg);
    va_end(arg);
    
    return r;
}


int ESPTerm::println(const char *format, ...) {
    std::va_list arg;
    va_start(arg, format);
    int r = ser->vprintf(format, arg);
    va_end(arg);
        
    r += ser->puts("\r\n");    
    return r;   
}


// ----- Colors -----


void ESPTerm::fg(Color c)
{
    int ci = c;
    if(ci > 7) {
        ci += (90-8);
    } else {
        ci += 30;
    }
    ser->printf("\033[%dm", ci);
}


void ESPTerm::bg(Color c)
{
    int ci = c;
    if(ci > 7) {
        ci += (100-8);
    } else {
        ci += 40;
    }
    ser->printf("\033[%dm", ci);
}


void ESPTerm::reset_attribs(void)
{
    ser->puts("\033[0m");
}


// ----- Cursor control & erasing -----


void ESPTerm::go_to(int y, int x)
{
    ser->printf("\033[%d;%dH", y, x);
}


void ESPTerm::clear_screen(ClearMode mode)
{
    ser->printf("\033[%dJ", mode);
}


void ESPTerm::clear_line(ClearMode mode)
{
    ser->printf("\033[%dK", mode);
}


void ESPTerm::screen_reset(void)
{
    ser->puts("\033c");
}


void ESPTerm::show_cursor(bool yes)
{
    if (yes)
        ser->puts("\033[?25h");
    else
        ser->puts("\033[?25l");
}


// ----- System commands -----


void ESPTerm::factory_reset(void)
{
    ser->puts("\033]FR\a");
}


void ESPTerm::set_screen_size(int rows, int cols)
{
    ser->printf("\033]W%d;%d\a", rows, cols);
}


bool ESPTerm::query_status(void)
{
    if (device_ok) return true;
    
    ser->puts("\033[5n");
    return false;    
}


// ----- Rx command parsing -----


void ESPTerm::ser_rx_char(void)
{
    while (ser->readable()) {
        char c = ser->getc();
        ansi_parser(&c, 1);  // pretend as if it was a string, this is safe         
    }    
}


void ESPTerm::apars_handle_plainchar(char c)
{
    if (c == 24) {
        if (on_esp_reset) on_esp_reset();    
    } 
    else if (c >= 1 && c <= 5) {
        if (on_button) on_button((int)c);        
    }
    else {
        if (on_char_rx) on_char_rx(c);            
    }
}


void ESPTerm::apars_handle_csi(char lead, const int* nums, char keychar)
{
    // Keyboard events
    if (on_key_press) {
        if (keychar == 'A') on_key_press(KEY_UP);
        else if (keychar == 'B') on_key_press(KEY_DOWN);
        else if (keychar == 'C') on_key_press(KEY_RIGHT);
        else if (keychar == 'D') on_key_press(KEY_LEFT);        
    }
    
    // Screen tapped / clicked
    if (on_mouse_click && keychar == 'M') {
        on_mouse_click(nums[0], nums[1]);    
    }
    
    // "Device OK" response to "Device Status Query"
    if (keychar == 'n' && nums[0] == 0) {
        device_ok = true;    
    }
       
    // ... other commands when added
}


void ESPTerm::apars_handle_osc(const char *str)
{
    // ... additional parsing when needed in later esp term versions
    if (on_osc_rx) on_osc_rx(str);
}


void ESPTerm::apars_handle_badseq(void)
{
    // Do nothing    
}



/* ----- Ragel constants block ------ */

/* #line 38 "ansi_parser_cpp.c" */
static const int ansi_start = 1;
//static const int ansi_first_final = 7;
//static const int ansi_error = 0;

//static const int ansi_en_CSI_body = 3;
//static const int ansi_en_OSC_body = 5;
//static const int ansi_en_main = 1;



/* Ragel generated parser */

/**
 * \brief Linear ANSI chars stream parser (Rage generated)
 *
 * Parses a stream of bytes using a Ragel parser. The defined
 * grammar does not use 'unget', so the entire buffer is
 * always processed in a linear manner.
 *
 * \attention -> but always check the Ragel output for 'p--'
 *            or 'p -=', that means trouble.
 *
 * \param newdata - array of new chars to process
 * \param len - length of the newdata buffer
 */
void ESPTerm::ansi_parser(const char *newdata, size_t len)
{
    if (len == 0) len = strlen(newdata);
    
    // Load new data to Ragel vars
    const char *p = newdata;
    const char *eof = NULL;
    const char *pe = newdata + len;

    // Init Ragel on the first run
    if (cs == -1) {
        
/* #line 77 "ansi_parser_cpp.c" */
    {
    cs = ansi_start;
    }

/* #line 65 "ansi_parser_cpp.rl" */
    }

    // The parser
    
/* #line 87 "ansi_parser_cpp.c" */
    {
    if ( p == pe )
        goto _test_eof;
    switch ( cs )
    {
tr0:
/* #line 75 "ansi_parser_cpp.rl" */
    {
            apars_handle_plainchar((*p));
        }
    goto st1;
st1:
    if ( ++p == pe )
        goto _test_eof1;
case 1:
/* #line 103 "ansi_parser_cpp.c" */
    if ( (*p) == 27 )
        goto st2;
    goto tr0;
st2:
    if ( ++p == pe )
        goto _test_eof2;
case 2:
    switch( (*p) ) {
        case 91: goto tr3;
        case 93: goto tr4;
    }
    goto tr2;
tr2:
/* #line 116 "ansi_parser_cpp.rl" */
    {
            apars_handle_badseq();
            {goto st1;}
        }
//    goto st0;
/* #line 123 "ansi_parser_cpp.c" */
//st0:
//cs = 0;
//    goto _out;
tr3:
/* #line 82 "ansi_parser_cpp.rl" */
    {
            /* Reset the CSI builder */
            csi_leading = csi_char = 0;
            csi_ni = 0;

            /* Zero out digits */
            for(int i = 0; i < CSI_N_MAX; i++) {
                csi_n[i] = 0;
            }

            {goto st3;}
        }
    //goto st7;
tr4:
/* #line 129 "ansi_parser_cpp.rl" */
    {
            /* Reset the OSC buffer */
            osc_i = 0;
            {goto st5;}
        }
    //goto st7;
//st7:
//    if ( ++p == pe )
//        goto _test_eof7;
case 7:
/* #line 154 "ansi_parser_cpp.c" */
    if ( (*p) == 27 )
        goto st2;
    goto tr0;
st3:
    if ( ++p == pe )
        goto _test_eof3;
case 3:
    if ( (*p) == 59 )
        goto tr7;
    if ( (*p) < 60 ) {
        if ( (*p) > 47 ) {
            if ( 48 <= (*p) && (*p) <= 57 )
                goto tr6;
        } else if ( (*p) >= 32 )
            goto tr5;
    } else if ( (*p) > 64 ) {
        if ( (*p) > 90 ) {
            if ( 97 <= (*p) && (*p) <= 122 )
                goto tr8;
        } else if ( (*p) >= 65 )
            goto tr8;
    } else
        goto tr5;
    goto tr2;
tr5:
/* #line 95 "ansi_parser_cpp.rl" */
    {
            csi_leading = (*p);
        }
    goto st4;
tr6:
/* #line 99 "ansi_parser_cpp.rl" */
    {
            /* x10 + digit */
            if (csi_ni < CSI_N_MAX) {
                csi_n[csi_ni] = csi_n[csi_ni]*10 + ((*p) - '0');
            }
        }
    goto st4;
tr7:
/* #line 106 "ansi_parser_cpp.rl" */
    {
            csi_ni++;
        }
    goto st4;
st4:
    if ( ++p == pe )
        goto _test_eof4;
case 4:
/* #line 204 "ansi_parser_cpp.c" */
    if ( (*p) == 59 )
        goto tr7;
    if ( (*p) < 65 ) {
        if ( 48 <= (*p) && (*p) <= 57 )
            goto tr6;
    } else if ( (*p) > 90 ) {
        if ( 97 <= (*p) && (*p) <= 122 )
            goto tr8;
    } else
        goto tr8;
    goto tr2;
tr8:
/* #line 110 "ansi_parser_cpp.rl" */
    {
            csi_char = (*p);
            apars_handle_csi(csi_leading, csi_n, csi_char);
            {goto st1;}
        }
   // goto st8;
//st8:
//    if ( ++p == pe )
//        goto _test_eof8;
case 8:
/* #line 228 "ansi_parser_cpp.c" */
    goto tr2;
tr9:
/* #line 135 "ansi_parser_cpp.rl" */
    {
            if (osc_i < OSC_BUF_LEN-1) {
                osc_buff[osc_i++] = (*p);
            }
        }
    goto st5;
st5:
    if ( ++p == pe )
        goto _test_eof5;
case 5:
/* #line 242 "ansi_parser_cpp.c" */
    switch( (*p) ) {
        case 7: goto tr10;
        case 27: goto st6;
    }
    goto tr9;
tr10:
/* #line 141 "ansi_parser_cpp.rl" */
    {
            /** Terminate the buffer */
            osc_buff[osc_i] = '\0';
            apars_handle_osc(osc_buff);
            
            {goto st1;}
        }
   // goto st9;
//st9:
//    if ( ++p == pe )
//        goto _test_eof9;
case 9:
/* #line 262 "ansi_parser_cpp.c" */
    goto tr2;
st6:
    if ( ++p == pe )
        goto _test_eof6;
case 6:
    if ( (*p) == 92 )
        goto tr10;
    goto tr2;
    }
    _test_eof1: cs = 1; goto _test_eof; 
    _test_eof2: cs = 2; goto _test_eof; 
    //_test_eof7: cs = 7; goto _test_eof; 
    _test_eof3: cs = 3; goto _test_eof; 
    _test_eof4: cs = 4; goto _test_eof; 
    //_test_eof8: cs = 8; goto _test_eof; 
    _test_eof5: cs = 5; goto _test_eof; 
    //_test_eof9: cs = 9; goto _test_eof; 
    _test_eof6: cs = 6; goto _test_eof; 

    _test_eof: {}
    if ( p == eof )
    {
    switch ( cs ) {
    case 1: 
    case 2: 
    case 3: 
    case 4: 
    case 5: 
    case 6: 
/* #line 116 "ansi_parser_cpp.rl" */
    {
            apars_handle_badseq();
            {goto st1;}
        }
 //   break;
/* #line 298 "ansi_parser_cpp.c" */
    }
    }

    //_out: {}
    }

/* #line 162 "ansi_parser_cpp.rl" */

}