/* * serial.e * * V1.00 * 2006-01-12 * * Brad Erickson brdrcksn+epsilon@gmail.com * * Access serial port from epsilon, similar to concurrent process. * * This should work with Epsilon version 12+ (it may still work with 11), * under any Win32 OS (uses kernel32.dll). * * Tested primarily under WinXP with Epsilon 12. * * * Commands: * * serial_start (on C-x, C-j) * Starts serial port buffer, prompting for COM port and Baud Rate. * * serial_set_options * Setup Parity, Databits, and Stopbits * * serial_port_close * Close serial port connection * * serial_close * Close serial port connection and window * * serial_previous_cmd (on ALT-p) * Like process-previous-cmd * * serial_next_cmd (on ALT-n) * Like process-next-cmd * * serial_enter_key (on Enter) * Sends a line to the serial port (only in linemode) * * serial_mode * Setup for serial mode * * serial_send_up (on ALT-Up) * Send an "Up" control code * * serial_send_down (on ALT-Down) * Send an "Down" control code * * serial_send_backspace (on ALT-Left) * Send a "Backspace" control code * * serial_complete (on Tab) * Do command completion (must setup SERIAL_MATCH_BUF first) * * * User Variables: * * serial_linemode * Set to 0 to send as you type * Set to 1 to wait for the enter key before sending * * serial_crlf * Translates received CarriageReturn-LineFeeds into LineFeed * * serial_local_echo * Turn on local echo * * serial_tab_completes * Turn on Tab Completion (see serial_complete function for setup) * * serial_coloring_rules (Bits) * Bit 1: Input/Output coloring * Bit 2: Simple ANSI coloring */ #include "eel.h" #include "proc.h" #include "colcode.h" #if EELVERSION < 110 #error Requires Epsilon Version 11 or later #endif #if EELVERSION >= 120 #define USE_WIDE_CHARS 1 #else #define USE_WIDE_CHARS 0 #endif #if EELVERSION >= 130 /* Version 13 supports this natively */ #define USE_CUSTOM_IDLE 0 #else /* To allow serial buffer refresh when not current buffer */ #define USE_CUSTOM_IDLE 1 #endif #if !USE_WIDE_CHARS #define byte char #endif /*****************************************************************************/ #define READBUF_SIZE 100 #define WRITEBUF_SIZE 200 #define SERIALBUF "serial" #define SERIAL_CMDS_BUF "-serial-cmds-buf" /* Put possible completion values into this buffer. */ #define SERIAL_MATCH_BUF "-serial-match-buf" /*****************************************************************************/ user int serial_linemode = 1; /* sends on enter key */ user int serial_crlf = 1; /* \r\n -> \n */ user int serial_local_echo = 0; /* local echo */ user int serial_tab_completes = 1; /* see serial_complete function */ user int serial_coloring_rules = 1 | 2;/* 1 for input/output, 2 for ansi */ int serial_comport = 1; /* Default COMM port */ int serial_baudrate = 115200; /* Default baud rate */ int serial_parity = 0; /* Default parity 0 = Off, 1 = On */ int serial_databits = 8; /* Default # data bits */ int serial_stopbits = 1; /* Default # stop bits */ /*****************************************************************************/ keytable serial_tab; spot serial_type_point; int serial_handle = -1; char _serial_mode_name[] = "Serial"; /*****************************************************************************/ /* Serial port driver definitions */ #define GENERIC_READ (0x80000000) #define GENERIC_WRITE (0x40000000) #define OPEN_EXISTING 3 #define MAXDWORD 0xffffffff typedef struct { int rit; int rttm; int rttc; int wttm; int wttc; } COMMTIMEOUT; /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ #if USE_CUSTOM_IDLE /* Use this if you want the serial port buffer to update even when it is * not in focus. * Epsilon 13 supports this natively. */ #define MAX_GLOBAL_WHEN_IDLE 5 int (*global_when_idle[MAX_GLOBAL_WHEN_IDLE])(); // Add this function to the list of idle-time functions globally. add_global_when_idle(int (*func)()) { int i; for (i = 0; i < MAX_GLOBAL_WHEN_IDLE; i++) if (global_when_idle[i] == func) // Already there. return; for (i = 0; i < MAX_GLOBAL_WHEN_IDLE; i++) if (!global_when_idle[i]) { global_when_idle[i] = func; return; } error("Too many global idle-time functions."); } // Remove this function from the list of global idle-time functions. // Do nothing if it's not on the list. delete_global_when_idle(int (*func)()) { int i; for (i = 0; i < MAX_GLOBAL_WHEN_IDLE; i++) if (global_when_idle[i] == func) global_when_idle[i] = 0; } int new_custom_when_idle(int cnt) { int i; int delay = -1; int new_delay = -1; for (i = 0; i < MAX_GLOBAL_WHEN_IDLE; i++) { if (global_when_idle[i]) { new_delay = (*global_when_idle[i])(cnt); if (delay < 0 || (new_delay >= 0 && new_delay < delay)) delay = new_delay; } } /* call original when_idle */ new_delay = custom_when_idle(cnt); if (delay < 0 || (0 <= new_delay && new_delay < delay)) delay = new_delay; return delay; } REPLACE_FUNC("custom", "when_idle"); #endif /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ int do_when_idle_serial(int cnt); /* Serial port driver functions */ serial_port_open(int comport) { COMMTIMEOUT timeout; int dcb[20], test[20]; char options[128]; serial_port_close(); sprintf(options, "com%d", comport); serial_handle = call_dll("kernel32.dll", #if USE_WIDE_CHARS "CreateFileW", #else "CreateFileA", #endif "p", "siiiiii", options, (GENERIC_READ | GENERIC_WRITE), 0, 0, OPEN_EXISTING, 0/*FILE_FLAG_OVERLAPPED*/, 0 ); if (serial_handle < 0) { serial_port_close(); error("unable to open serial port"); } timeout.rit = MAXDWORD; timeout.rttm = 0; timeout.rttc = 0; timeout.wttm = 0; timeout.wttc = 0; call_dll("kernel32.dll", "SetCommTimeouts", "p", "ip", serial_handle, &timeout ); call_dll("kernel32.dll", "GetCommState", "p", "ip", serial_handle, dcb ); sprintf(options, "baud=%d parity=%s data=%d stop=%d to=on", serial_baudrate, serial_parity ? "Y" : "N", serial_databits, serial_stopbits ); call_dll("kernel32.dll", #if USE_WIDE_CHARS "BuildCommDCBAndTimeoutsW", #else "BuildCommDCBAndTimeoutsA", #endif "p", "spp", options, dcb, test ); call_dll("kernel32.dll", "SetCommState", "p", "ip", serial_handle, dcb ); #if USE_CUSTOM_IDLE if (find_index("add_global_when_idle") != 0) { add_global_when_idle(do_when_idle_serial); } else { add_buffer_when_idle(do_when_idle_serial); } #endif } byte serial_read_prevchar = '\0'; //? char serial_port_read_char(char c) { int cmd_start = *serial_type_point; save_var bufnum = name_to_bufnum(SERIALBUF); save_var call_on_modify = 0; if (point != *serial_type_point) { save_spot point = *serial_type_point; } switch (c) { case '\r': insert((serial_crlf) ? '\n' : c); break; case '\n': if ((!serial_crlf) || (serial_read_prevchar != '\r')) { insert(c); } break; case '\b': delete(point - 1, point); break; case '\x07': maybe_ding(bell_on_bad_key); break; default: insert(c); break; } if (serial_coloring_rules & 1) { set_character_color(cmd_start-1, point, color_class process_output); set_tagged_region("needs-color", cmd_start-1, point, 0); } serial_read_prevchar = c; *serial_type_point = point; } int serial_port_read() { byte serial_bbuf[READBUF_SIZE+1]; int numread, total_numread; int j; int save_start = *serial_type_point; save_var bufnum = name_to_bufnum(SERIALBUF); save_var call_on_modify = 0; if ((serial_handle < 0) || (!exist(SERIALBUF))) { return -1; } total_numread = 0; do { call_dll("kernel32.dll", "ReadFile", "p", "ipipi", serial_handle, serial_bbuf, READBUF_SIZE, &numread, 0 /* NULL */ ); total_numread += numread; for (j = 0; j < numread; j++) { serial_port_read_char((char)serial_bbuf[j]); } } while ((numread > 0) && (total_numread < 1000)); if (serial_coloring_rules & 2) { serial_color_ansi_output(save_start); } return total_numread; } int serial_port_send(byte *writebuf, int len) { int numwritten; if (serial_handle < 0) { return -1; } call_dll("kernel32.dll", "WriteFile", "p", "ipipi", serial_handle, writebuf, len, &numwritten, 0 /* NULL */ ); return numwritten; } /*****************************************************************************/ #define MAX_SER_CMD_NUM 128 #define MAX_SER_CMD_LEN 128 serial_cmds_add_to_end(int cmd_start, int cmd_end) { int num_cmds; char new_cmd[MAX_SER_CMD_LEN]; char old_cmd[MAX_SER_CMD_LEN]; new_cmd[0] = '\0'; if (cmd_end - cmd_start < MAX_SER_CMD_LEN) { grab(cmd_start, cmd_end, new_cmd); } save_var bufname = SERIAL_CMDS_BUF; point = 0; do_uniq(1, 1, 0); /* discard adjacent duplicate cmds silently */ num_cmds = lines_between(0, size()); if (num_cmds >= MAX_SER_CMD_NUM) { go_line(num_cmds - MAX_SER_CMD_NUM); nl_forward(); delete(0, give_begin_line()); } if (new_cmd[0] && !index(new_cmd, '\n')) { point = 0; do { grab(give_begin_line(), give_end_line(), old_cmd); if (strcmp(new_cmd, old_cmd) == 0) { delete(give_begin_line(), give_end_line() + 1); nl_reverse(); } } while (nl_forward()); point = size(); bprintf(new_cmd); bprintf("\n"); } } serial_get_previous_cmd(int down) { int buf = bufnum; char cmd[FNAMELEN]; int cmd_start = *serial_type_point; if (!serial_linemode) error("Must be in Line Mode"); point = size(); if (!exist(SERIAL_CMDS_BUF)) return; *cmd = 0; if (has_arg) { iter = 0; save_var bufname = SERIAL_CMDS_BUF; if (point == size()) point--; to_begin_line(); restore_vars(); if (!get_choice(name_to_bufnum(SERIAL_CMDS_BUF), cmd, "Previous commands", "Select a previous command", "OK", "Cancel", "")) return; } delete(cmd_start, size()); if (*cmd) { bprintf("%s", cmd); } else { save_var bufname = SERIAL_CMDS_BUF; down_or_up_lines(down); buf_xfer(buf, give_begin_line(), give_end_line()); } } serial_send_data() { char *serial_cbuf; int cmd_start = *serial_type_point; int cmd_end = size(); if (cmd_start >= cmd_end) { return; } serial_cbuf = (char *)malloc((cmd_end - cmd_start) * sizeof(char)); grab(cmd_start, cmd_end, serial_cbuf); if (!serial_local_echo) { delete(cmd_start, cmd_end); } do_serial_send_string(serial_cbuf); free(serial_cbuf); *serial_type_point = cmd_end; } /* Epsilon 13 and above calls this automatically when idle. * For previous versions, this will be called through the custom * global_when_idle functions. */ int serial_when_idle_cnt = 0; int do_when_idle_serial(int cnt) { int num_read; if ((serial_handle < 0) || (!exist(SERIALBUF))) { serial_port_close(); return -1; } if (!serial_linemode && (bufnum == name_to_bufnum(SERIALBUF))) { serial_send_data(); } num_read = serial_port_read(); if (num_read > 0) { maybe_refresh(); } if ((num_read > 0) || (serial_when_idle_cnt >= cnt)) { serial_when_idle_cnt = cnt; } return MIN(1 + cnt - serial_when_idle_cnt, 30); } do_serial_set_options(int level) { char tempstr[256]; int tempval; save_var recall_id; recall_id = "serial_comport"; get_str_auto_def(tempstr, "COM[1-4]"); tempval = strtoi(tempstr, 10); switch (tempval) { case 1: case 2: case 3: case 4: serial_comport = tempval; break; default: error("Bad COM Port"); break; } if (level >= 1) { recall_id = "serial_baudrate"; get_str_auto_def(tempstr, "Baud Rate"); tempval = strtoi(tempstr, 10); switch (tempval) { case 9600: case 19200: case 38400: case 57600: case 115200: serial_baudrate = tempval; break; default: error("Bad Baudrate"); break; } } if (level >= 2) { recall_id = "serial_parity"; get_str_auto_def(tempstr, "Parity(Y/N)"); switch(toupper(tempstr[0])) { case 'Y': serial_parity = 1; break; case 'N': serial_parity = 0; break; default: error("Bad Parity Setting"); break; } recall_id = "serial_databits"; get_str_auto_def(tempstr, "Data Bits (7-8)"); tempval = strtoi(tempstr, 10); switch(tempval) { case 7: case 8: serial_databits = tempval; break; default: error("Bad Data Bits Setting"); break; } recall_id = "serial_stopbits"; get_str_auto_def(tempstr, "Stop Bits (1-2)"); tempval = strtoi(tempstr, 10); switch(tempval) { case 1: case 2: serial_stopbits = tempval; break; default: error("Bad Data Bits Setting"); break; } } say("COM %d, baud=%d, parity=%s, data=%d, stop=%d", serial_comport, serial_baudrate, serial_parity ? "Y" : "N", serial_databits, serial_stopbits ); } serial_on_modify() { int cmd_start = *serial_type_point; if (serial_handle >= 0) { if (point < cmd_start) { error("Cannot modify here."); } else if ((last_index == backward_delete_character) && (point == cmd_start)) { error("Cannot back over typepoint."); } } save_var call_on_modify; normal_on_modify(); } /*****************************************************************************/ /* serial port commands */ command serial_port_close() { #if USE_CUSTOM_IDLE if (find_index("delete_global_when_idle") != 0) { delete_global_when_idle(do_when_idle_serial); } else { delete_buffer_when_idle(do_when_idle_serial); } #endif if (serial_handle > 0) { call_dll("kernel32.dll", "CloseHandle", "p", "i", serial_handle ); serial_handle = -1; } } command serial_close() { serial_port_close(); if (exist(SERIALBUF)) { delete_user_buffer(SERIALBUF); } } command serial_previous_cmd() on serial_tab[ALT('p')] { serial_get_previous_cmd(0); } command serial_next_cmd() on serial_tab[ALT('n')] { serial_get_previous_cmd(1); } command serial_enter_key() on serial_tab['\n'], serial_tab['\r'] { int cmd_start = *serial_type_point; int cmd_end; if (!serial_linemode || (point < cmd_start) || (serial_handle < 0)) { enter_key(); return; } to_end_line(); cmd_end = point; if (!exist(SERIAL_CMDS_BUF)) { create(SERIAL_CMDS_BUF); } nl_forward(); if (cmd_end > cmd_start) { serial_cmds_add_to_end(cmd_start, cmd_end); } insert('\n'); serial_send_data(); } command serial_set_options() { do_serial_set_options(2); } command serial_start() on cx_tab[CTRL('j')] { if (exist(SERIALBUF)) { locate_window(SERIALBUF, ""); if (*serial_type_point > size()) { *serial_type_point = size(); } point = *serial_type_point; } else { if (has_arg) { serial_comport = iter; } else { do_serial_set_options(1); } zap(SERIALBUF); to_buffer(SERIALBUF); serial_type_point = alloc_spot(); *serial_type_point = point = 0; serial_mode(); discardable_buffer = 1; buffer_not_saveable = 1; set_read_only(0); } if (serial_handle == -1) { serial_port_open(serial_comport); } } serial_color_range(from, to) { if (from >= to) return to; save_var point, matchstart, matchend; /* stuff we type is colored process_input, * stuff received is colored process_output */ set_character_color(*serial_type_point, size(), color_class process_input); set_tagged_region("needs-color", *serial_type_point, size(), 0); return to; } command serial_mode() { mode_default_settings(); mode_keys = serial_tab; major_mode = _serial_mode_name; make_mode(); if (want_code_coloring && (serial_coloring_rules & 1)) { recolor_range = serial_color_range; /* set up coloring rules */ recolor_from_here = recolor_from_top; when_setting_want_code_coloring(); } call_on_modify = 1; buffer_on_modify = serial_on_modify; } do_serial_send_string(char *pc) { byte *serial_bbuf; int len; len = strlen(pc); #if USE_WIDE_CHARS serial_bbuf = (byte *)malloc(len * sizeof(byte)); chars_to_bytes(serial_bbuf, pc); serial_port_send(serial_bbuf, len); free(serial_bbuf); #else serial_port_send(pc, len); #endif } command serial_send_up() on serial_tab[NUMALT(KEYUP)] { do_serial_send_string("\x1b[A"); } command serial_send_down() on serial_tab[NUMALT(KEYDOWN)] { do_serial_send_string("\x1b[B"); } command serial_send_backspace() on serial_tab[NUMALT(KEYLEFT)] { do_serial_send_string("\b"); } /*****************************************************************************/ /* Add sorted command list for completion here */ char serial_cmd_list[] = "aTest\n" "bTest\n" "cTest\n" ; /*****************************************************************************/ char serial_complete_s[TAGLEN]; /*****************************************************************************/ command serial_complete() on serial_tab['\t'] { char *s; int start_point, orig_point; int flags = COMP_FOLD; if (!serial_tab_completes || (point < *serial_type_point) || (serial_handle < 0)) { normal_character(); return; } if (!serial_linemode) error("Must be in Line Mode"); orig_point = point; re_search(-1, word_pattern); start_point = point; point = orig_point; if (orig_point - start_point >= TAGLEN) error("Too long for tab complete"); if (!exist(SERIAL_MATCH_BUF)) { zap(SERIAL_MATCH_BUF); buffer_printf(SERIAL_MATCH_BUF, serial_cmd_list); } if (prev_cmd != CMD_PROC_COMPL) { zap(_MATCH_BUF); bufname = SERIAL_MATCH_BUF; xfer(_MATCH_BUF, 0, size()); bufname = SERIALBUF; /* Assume already sorted */ grab(start_point, orig_point, serial_complete_s); flags |= STARTMATCH; } s = general_matcher(serial_complete_s, flags); if (s != NULL) { delete(start_point, orig_point); bprintf(s); } this_cmd = CMD_PROC_COMPL; } serial_color_ansi_output(int start) { save_var point = start - 25; int start_ansi; to_begin_line(); start_ansi = point; if (search(1, "\x1B")) { point = start_ansi; while (re_search(1, "\x1B%[([0-9]+);([0-9]+);([0-9]+m)[^\n\x1B]*" "(\x1B%[0*m)")) color_ansi_output_section(3); point = start_ansi; while (re_search(1, "\x1B%[([0-9]+);([0-9]+m)[^\n\x1B]*(\x1B%[0*m)")) color_ansi_output_section(2); point = start_ansi; while (re_search(1, "\x1B%[([0-9]+m)[^\n\x1B]*(\x1B%[0*m)")) color_ansi_output_section(1); point = start_ansi; while (re_search(1, "\x1B%[0*m")) // Remove stray resets. delete(matchstart, matchend); } }