GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

Einfaches Terminalprogramm für serielle Kommunilation unter Linux/MacOSX in C

Simple terminal serial port program for Linux/MacOSX in C

For debugging micro controllers in "release mode" a simple "old school" serial communication, or a similar USB implementation, is still an often used method. A common way of accessing the port is to use stty in combination with cat or screen, e.g. for 9600 baud, 8 data bits, 1 stop bit and no parity check:

$ stty -F /dev/ttyX raw ispeed 9600 ospeed 9600 cs8 -cstopb ignpar && cat /dev/ttyX

or

$ stty -F /dev/ttyX raw ispeed 9600 ospeed 9600 cs8 -cstopb ignpar && screen /dev/ttyX

... where ttyX is the name of the port file (and screen has options as well to configure the communication). However, the settings of the port are (depending on the platform) permanently changed, cat does not allow interactive input, and to exit screen you need to type CTRL-A ->k ->y. That can be a bit annoying. This little implementation in C simply forwards the raw bytes and restores the settings on exit. To exit a short CTRL-C is enough. See the source, and if you like it get it here, say make and start using it:

Zum Debugging von Microcontrollern wird häufig auf die "gute alte" serielle Schnittstelle (V.24/RS232) oder eine entsprechende USB-basierte Implementation zurückgegriffen. Unter Linux/BSD/MacOS wird darauf üblicherweise direkt von der Shell zugegriffen, Beispielsweise mit 9600 Baud, 8 Datenbits, 1 Stopbit und keiner Paritätsprüfung:

$ stty -F /dev/ttyX raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar && cat /dev/ttyX

oder

$ stty -F /dev/ttyX raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar && screen /dev/ttyX

... wobei ttyX der Dateiname der Portdatei in /dev/ ist. Die Porteinstellungen werden dabei (abhängig von der Plattform) von stty permanent überschrieben. Mit cat ist keine interaktive Eingabe möglich, und screen hat seinen eigenen Puffer und (curses) Anzeigemanagement - man muss CTRL-A ->k ->y drücken bis man es wieder beendet hat. Das kann alles ein wenig ärgerlich sein. Eine "lightweight"-Alternative ist dieses kleine C-Programm. Es leitet die Rohdaten (stdin vom Terminal nach seriell, seriell zu stdout) um und stellt beim Beenden den normalen Port-Kontext wieder her. Zum Beenden reicht CTRL-C. Einfach einen Blick auf den Quelltext und die folgende Bedienungsanmerkung werfen - und wenn's gefällt hier herunterladen und make sagen ...

#
# First, the --help :-)
#
Usage: serialport [options] <portfile> [BAUD=115200[PARITY=n[DATABITS=8[STOPBITS=1]]]]

Simple serial port TTY redirection program for (raw) microcontroller
communication. Exit with CTRL-C.

Options:

 -h, --help          Print this help
 -v, --verbose       Make the program more chatty (on stderr)
 -T, --timeout=N     Exit after nothing happened for N seconds
 -l, --lines=N       Exit after receiving N lines
 -d, --disconnect=S  Disconnect when receiving this (\n etc allowed)

 -t, --timestamp     Add timestamp before each line
 -e, --local-echo    Print the sent characters locally
 -w, --char-wait=N   Wait N seconds before sending the next byte
 -W, --start-wait=N  Wait N seconds after opening port before doing anything
 -c, --color         Print with colors
 -n, --control-chars Print non-printable control characters
 -D, --debug         Print debug messages and dumps (stderr)

Examples:

 serialport /dev/ttyS               Connect with default values

 serialport /dev/ttyS 115200        Set baud rate, rest default

 serialport /dev/ttyS 9600N81       Set baud rate, parity, data bits, stop bits

 serialport /dev/ttyS -T 0.5        Exit after 0.5s inactivity

 serialport /dev/ttyS -d 'Bye\\r'   Exit when receiving 'Bye<CR>'

 serialport /dev/ttyS -vect         Interactive mode with colors, local
                                    echo and timestamps for received lines

 echo 'hello!' | serialport -W1.0 /dev/ttyS

                                    Wait 1s before sending the line 'hello!'

 cat cmds | serialport -t -l 10 -T 0.5 /dev/ttyS | grep 'data'
                                    Send a file, fetch max 10 lines, quit after
                                    idle port for 0.5s and pipe to grep to fetch
                                    lines containing the text 'data'

Exit codes (for shell programming):

 0 = OK, no error
 1 = Wrong argument, you mistyped something or the program can't handle it.
 2 = Signal caught, e.g. CTRL-C, broken pipe, kill, etc.
 3 = Fatal error. Something went wrong internally (memory, bugs(?) etc).
 4 = I/O error, e.g. Disk full, USB-serial-converted disconnected ...

(serialport v1.0, stfwi)
# Interactive mode with verbose (print the reason why the program did quit,
# the exit code, and statistics).
# Colors: RED:error, CYAN:sent, BLUE:received, GREEN:Finished without error, GRAY:Debug

$ serialport -vec /dev/tty.usb

HHeelllloo!!

112233

445566

778899

[Exit due to signal: CTRL-C][E0]
[TX:19, RX:20, RX:4 lines]
# Data to sent are piped in.

$ echo -e '1234567890\nABC\n' | serialport -ce /dev/tty.usb

1234567890
ABC

1234567890
ABC
# The same as above, only that a character send delay is added (and quit after 0.5 seconds inactivity).
# Character delays do not change the send baudrate, but it leaves small controllers time to process the data.
# The program still receives at full speed.

$ echo -e '1234567890\nABC\n' | serialport -ce -w0.2 -T0.5  /dev/tty.usb

11223344556677889900

AABBCC
# Interactive, colors, quit on receiving "!<NEW LINE>". New line can be either CR or LF or both.

$ serialport -vc -d '!\n' /dev/tty.usb

Hello!!?
Hello!!

[Exit due to disconnect command][E0]
[TX:17, RX:18, RX:2 lines]
# Pipe in, send and receive until there is inactivity for 0.5 seconds. Then quit.

$ echo -e '1234567890\nABC\n' | serialport -vct -T0.5  /dev/tty.usb

1380545190.24 1234567890
1380545190.24 ABC

[Timed out after 0.5s][E0]
[TX:16, RX:17, RX:3 lines]
# Pipe in a file, quit after having received 10 lines (separated by CR, LF or both)

$ cat test.txt | serialport -vct -l 10 /dev/tty.usb

1380545314.35 1 ABCDEFGHIJKL
1380545314.35 2 ABCDEFGHIJKL
1380545314.35 3 ABCDEFGHIJKL
1380545314.35 4 ABCDEFGHIJKL
1380545314.35 5 ABCDEFGHIJKL
1380545314.36 6 ABCDEFGHIJKL
1380545314.36 7 ABCDEFGHIJKL
1380545314.36 8 ABCDEFGHIJKL
1380545314.36 9 ABCDEFGHIJKL
1380545314.36 0 ABCDEFGHIJKL

[Specified number of lines received][E0]
[TX:195, RX:168, RX:10 lines]
# If something goes wrong, errors are RED
# Wrong TTY

$ serialport -vc /dev/YYT

[Failed to open port: /dev/YYT][E4]
[TX:0, RX:0, RX:0 lines]


# Pulled the USB plug ...

$ serialport -vc /dev/tty.usb

[I/O port error: Inappropriate ioctl for device][E4]
[TX:0, RX:2, RX:0 lines]
# Very high verbosity (DEBUG mode). Prints settings of locals and port, etc:

$ echo -e '1234\n5678\n90\n' | serialport -D -vcet -W0.5 -T0.5 -d '8' /dev/tty.usb > test.txt 2>&1

parse_args() start_wait=0
parse_args() timeout=0.500000
parse_args() disconnect=8
parse_args() portfile=/dev/tty.usb
main() Init serial port
serial_port = {
  file           = /dev/tty.usb
  baudrate       = 115200
  parity         = n
  databits       = 8
  stopbits       = 1
  local          = 1
  flush          = 1
  set_dtr        = -1
  set_rts        = -1
  timeout_us     = 1000
  _state = {
    error_code   = 0
    error_string =

    handle       = 3
    mdlns        = 00000026h
    mdlns_orig   = 00000026h
    attr = {
      c_iflag    = 00000004h
      c_oflag    = 00000000h
      c_cflag    = 00038b00h
      c_lflag    = 00000000h
      c_cc       = (string)
      c_ispeed   = 115200
      c_ospeed   = 115200
    }
    attr_orig = {
      c_iflag    = 00000000h
      c_oflag    = 00000000h
      c_cflag    = 00034b00h
      c_lflag    = 00000000h
      c_cc       = (string)
      c_ispeed   = 9600
      c_ospeed   = 9600
    }
  }
};
main() Local input is STDIN
main() Local input is STDOUT
main() Local input is NO TTY
main() Local output is NO TTY
main() Prepare run
main() Initial timeout: 0.500000
main() Run ...
[RX 1]
1380547573.27 [TX 14]
1234
5678
90

[EOF IN]
[RX 14]
1234
1380547573.28 5678
1380547573.28 90

DETECTED '8'
eexit(0, Exit due to disconnect command)

[Exit due to disconnect command][E0]
[TX:14, RX:15, RX:4 lines]
eexit() EXIT

Sorce code

Quelltext

Main file

Main-Datei

/**
 * Simple serial port terminal interface and configuration.
 *
 * Reads local stdin and forwards the characters (raw) to the
 * port. Reads the port and forwards to local stdout. Error
 * messages (and verbose messages) are printed to stderr.
 *
 * - Restores the port and terminal settings on exit.
 *
 * - Implemented exit signals: SIGTERM,SIGKILL,SIGINT,SIGHUP,
 *                             SIGABRT,SIGTRAP,SIGPIPE,SIGSYS
 *
 * - Defaults:
 *
 *   - Baudrate = 115200
 *   - Parity   = N
 *   - Databits = 8
 *   - Stopbits = 1
 *
 * @file main.c
 * @license GPL3
 * @author stfwi
 */
#include "serialport.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <getopt.h>
#include <math.h>
#include <limits.h>
#include <time.h>
#include <sys/time.h>
#include <locale.h>
 
#define PRG_NAME ("serialport")
#define PRG_VERSION ("1.0")
#define debug(...) { if(is_debug) _debug(__VA_ARGS__); }
 
/**
 * Program exit codes
 */
enum exit_codes {
  EC_OK = 0, EC_ARG, EC_SIGNAL, EC_FATAL, EC_IO, EC_END
};
 
/**
 * Output colouring
 */
enum color_ids {
  COLOR_NO = 0, COLOR_OK, COLOR_ERR, COLOR_RX, COLOR_TX, COLOR_TS, COLOR_DBG, COLOR_END
};
 
const char *color_strings[] = {
  "\033[0m",    // reset
  "\033[0;32m", // green
  "\033[0;31m", // red
  "\033[0;34m", // RX: blue
  "\033[1;36m", // TX echo: light cyan
  "\033[0;35m", // Timestamp: purple
  "\033[0;37m", // Debug: light gray
  "\033[0m",    // --end--
};
 
/**
 * Globals
 */
int in_d = -1;                          // Input file descriptor (def. STDIN)
int out_d = -1;                         // Output file descriptor (def. STDOUT)
int in_is_tty = 0;                      // Local input is a TTY
int out_is_tty = 0;                     // Local output is a TTY
int in_eof = 0;                         // Local input is EOF (if no TTY)
int exit_counter = 0;                   //
struct termios stdios;                  // Local TTY settings
struct termios stdios_orig;             // Local TTY startup settings (for restore)
serial_port_t port;                     // Serial port
double timeout = NAN;                   // Inactivity timeout "counter"
int was_newline = 0;                    // Detection of <CR><LF> combinations
int was_cr = 0;
long long next_char_wait_count = 0;     // counter, send wait
unsigned long long lines_received  = 0; // statistics, counter, exit condition
unsigned long long bytes_rx = 0;        // statistics, counter
unsigned long long bytes_tx = 0;        // statistics, counter
struct { int p; char s[256]; int sz; } buffer;  // RX command buffer
int disconnect_cmd_len = 0;
enum color_ids color_last = COLOR_END;
 
/**
 * Globals (Config)
 */
char    portfile[256];
char    infile[256];            // maybe later overwritable, now stdin
char    outfile[256];           // maybe later overwritable, now stdout
char    disconnect_cmd[256];    // Exit on receiving this
int     baudrate        = 115200;
char    parity          = 'n';
int     stopbits        = 1;
int     databits        = 8;
int     is_debug        = 0;    // bool Debug messages, implicitly enable is_verbose
int     is_verbose      = 0;    // bool Verbose
int     is_local_echo   = 0;    // bool Echo sent chars to stdout
double  timeout_cfg     = NAN;  // Exit after inactivity of this in seconds
int     add_timestamps  = 0;    // bool
long    character_wait  = 0;    // us
long    start_wait      = 0;    // us
int     use_colors      = 0;    // bool
int     nonprintable    = 0;    // bool
int     max_lines       = -1;   // Exit after receiving max_lines
 
 
void eexit(int code, const char* err_msg, ...);
 
 
/**
 * Print usage and exit.
 *
 * @return void
 */
void print_usage(void)
{
  char placeholder[64];
  memset(placeholder, ' ', 64);
  placeholder[strlen(PRG_NAME)] = '\0';
  printf(
      "Usage: %s [options] <portfile> [BAUD=%d[PARITY=%c[DATABITS=%d[STOPBITS=%d]]]]\n"
      "\n"
      "Simple serial port TTY redirection program for (raw) microcontroller\n"
      "communication. Exit with CTRL-C.\n"
      "\n"
      "Options:\n\n"
      " -h, --help          Print this help\n"
      " -v, --verbose       Make the program more chatty (on stderr)\n"
      " -T, --timeout=N     Exit after nothing happened for N seconds\n"
      " -l, --lines=N       Exit after receiving N lines\n"
      " -d, --disconnect=S  Disconnect when receiving this (\\n etc allowed) \n"
      " -t, --timestamp     Add timestamp before each line\n"
      " -e, --local-echo    Print the sent characters locally\n"
      " -w, --char-wait=N   Wait N seconds before sending the next byte\n"
      " -W, --start-wait=N  Wait N seconds after opening port before doing anything\n"
      " -c, --color         Print with colors\n"
      " -n, --control-chars Print non-printable control characters\n"
      " -D, --debug         Print debug messages and dumps (stderr)\n"
      "\n"
      "Examples:\n\n"
      " %s /dev/ttyS               Connect with default values\n\n"
      " %s /dev/ttyS 115200        Set baud rate, rest default\n\n"
      " %s /dev/ttyS 9600N81       Set baud rate, parity, data bits, stop bits\n\n"
      " %s /dev/ttyS -T 0.5        Exit after 0.5s inactivity\n\n"
      " %s /dev/ttyS -d 'Bye\\\\r'   Exit when receiving 'Bye<CR>'\n\n"
      " %s /dev/ttyS -vect         Interactive mode with colors, local\n"
      " %s                         echo and timestamps for received lines\n\n"
      " echo 'hello!' | %s -W1.0 /dev/ttyS \n"
      " %s                         Wait 1s before sending the line 'hello!'\n\n"
      " cat cmds | %s -t -l 10 -T 0.5 /dev/ttyS | grep 'data'\n"
      " %s                         Send a file, fetch max 10 lines, quit after\n"
      " %s                         idle port for 0.5s and pipe to grep to fetch\n"
      " %s                         lines containing the text 'data'\n"
      "\n"
      "Exit codes (for shell programming):\n\n"
      " 0 = OK, no error\n"
      " 1 = Wrong argument, you mistyped something or the program can't handle it.\n"
      " 2 = Signal caught, e.g. CTRL-C, broken pipe, kill, etc.\n"
      " 3 = Fatal error. Something went wrong internally (memory, bugs(?) etc).\n"
      " 4 = I/O error, e.g. Disk full, USB-serial-converted disconnected ...\n"
      "\n"
      "(%s v%s, stfwi)\n",
      PRG_NAME, baudrate, parity, databits, stopbits,
      PRG_NAME, PRG_NAME, PRG_NAME, PRG_NAME, PRG_NAME, PRG_NAME, placeholder,
      PRG_NAME, placeholder, PRG_NAME, placeholder, placeholder, placeholder,
      PRG_NAME, PRG_VERSION);
  exit(1);
}
 
/**
 * Write a color definition to a defined
 *
 * @param enum colors color
 * @param int where 0=stdin 1=stderr
 */
void set_color(enum color_ids color, int where) {
  if(!use_colors || color >= COLOR_END || color < 0 || color == color_last) return;
  if(where) {
    fprintf(stderr, "%s", color_strings[color]);
  } else {
    if(write(out_d, color_strings[color], strlen(color_strings[color])) < 0) {
      eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
    }
  }
}
 
/**
 * Debug messages
 * @param const char *fmt
 * @param ...
 */
void _debug(const char* fmt, ...)
{
  char ss[256];
  int i;
  va_list args;
  va_start (args, fmt);
  vsnprintf (ss, sizeof(ss), fmt, args);
  va_end (args);
  for(i=strlen(ss)-1; i>=0; i--) {
    if(ss[i]=='\r' || ss[i]=='\n') ss[i]= '^';
  }
  if(!strlen(ss)) return;
  set_color(COLOR_DBG,1);
  fprintf(stderr, "%s", ss);
  set_color(COLOR_NO, 1);
  fprintf(stderr, "\n");
}
 
 
/**
 * Prints a nonprintable character to stdout
 * @param char c
 */
void print_nonprintable(char c)
{
  if(nonprintable && c < 32) {
    char es[5] = {0,0,0,0,0};
    snprintf(es, sizeof(es)-1, "%lc", 0x2400+c);
    if(write(out_d, es, strlen(es)) < 0) {
      eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
    }
  }
}
 
 
/**
 * Converts an argument to int with checks (numeric, integer, range min..max).
 * The range check can be ignored by setting min > max. Returns INVI on failure.
 * @param const char *s
 * @param int min
 * @param int max
 * @param double k
 * @return int
 */
int int_arg(const char *s, int min, int max, double k)
{
  #define INVI (INT_MAX-2)
  double d; int i;
  return (!s||isnan(d=atof(s)*k)||((i=(int)d)!=d)||(min<max&&(i<min||i>max)))?INVI:i;
}
 
 
/**
 * Converts an argument to double with check
 * The range check can be ignored by setting min > max. Returns NaN on failure.
 * @param const char *s
 * @param double min
 * @param double max
 * @return double
 */
double dbl_arg(const char *s, double min, double max)
{
  double d=atof(s);
  return (!s || isnan(d) || ((min<max) && (d<min||d>max)) ) ? NAN : d;
}
 
 
/**
 * Copies a string argument, where special character like \n \r \t are decoded.
 * @param char *v
 * @param const char *s
 * @param size_t n
 * @return char*
 */
char* str_arg(char *v, const char *s, size_t n)
{
  int i, j;
  memset(v,0,n);
  for(i=0, j=0; j<n && i<n && s[i]; i++, j++) {
    if(s[i] == '\\' && s[i+1] != '\0') {
      switch(s[++i]) {
        case 'r' : v[j] = '\r'; break;
        case 'n' : v[j] = '\n'; break;
        case 't' : v[j] = '\t'; break;
        case 'f' : v[j] = '\f'; break;
        case 'a' : v[j] = '\a'; break;
        case 'b' : v[j] = '\b'; break;
        case 'v' : v[j] = '\v'; break;
        case '\\': v[j] = '\\'; break;
        default:
          debug("str_arg() unrecognised character '\\%c'", s[i]);
      }
    } else {
      v[j] = s[i];
    }
  }
  if(j>=n || i>=n) {
    eexit(EC_FATAL, "Allocated memory not big enough to contain a string argument");
  }
  return v;
}
 
 
/**
 * Parses the command line arguments
 *
 * @param int argc
 * @param const char **argv
 * @return void
 */
void parse_args(int argc, char **argv)
{
  int n, c;
  debug("parse_args(%d, argv)", argc);
 
  const struct option long_options[] = {
      {"help", 0, 0, 0},
      {"verbose", 0, 0, 0},
      {"local-echo", 0, 0, 0},
      {"timeout", 1, 0, 0},
      {"debug", 0, 0, 0},
      {"disconnect", 1, 0, 0},
      {"timestamp", 0, 0, 0},
      {"color", 0, 0, 0},
      {"control-chars", 0, 0, 0},
      {"lines", 1, 0, 0},
      {"char-wait", 1, 0, 0},
      {"start-wait", 1, 0, 0},
      {NULL, 0, NULL, 0}
  };
 
  if(argc<2) print_usage();
 
  while ((c = getopt_long(argc, argv, "ncDhved:tT:l:w:W:", long_options, &n)) != -1) {
    //debug("parse_args() optindex=%d", optind);
    if(!c) {
      //debug("parse_args() longopt=%s", long_options[n].name);
      switch(n) {
        case 0  : c='h'; break;
        case 1  : c='v'; break;
        case 2  : c='e'; break;
        case 3  : c='T'; break;
        case 4  : c='D'; break;
        case 5  : c='d'; break;
        case 6  : c='t'; break;
        case 7  : c='c'; break;
        case 8  : c='n'; break;
        case 9  : c='l'; break;
        case 10 : c='w'; break;
        case 11 : c='W'; break;
        default: if(optind>0) eexit(EC_ARG, "Invalid option %s", argv[optind-1]);
      }
    }
    //debug("parse_args() shortopt=%c", c);
    switch (c) {
      case 'h': print_usage(); break;
      case 'v': is_verbose=1; break;
      case 'e': is_local_echo=1; break;
      case 'D': is_verbose=1; is_debug=1; break;
      case 't': add_timestamps = 1; break;
      case 'c': use_colors = 1; break;
      case 'n': nonprintable = 1; break;
      case 'T':
        if(isnan(timeout_cfg = dbl_arg(optarg, 0, 1000000))) {
          eexit(EC_ARG, "Invalid timeout (0 to 1e6) seconds: %s", optarg);
        }
        debug("parse_args() timeout=%f", timeout_cfg);
        break;
      case 'd':
        if(!optarg) {
          eexit(EC_ARG, "Disconnect command option (-d) needs a text argument");
        } else {
          str_arg(disconnect_cmd, optarg, sizeof(disconnect_cmd));
          debug("parse_args() disconnect=%s", disconnect_cmd);
          if(strlen(disconnect_cmd) >= buffer.sz) {
            eexit(EC_ARG, "Disconnect command is too long, sorry");
          }
        }
        break;
      case 'l':
        if((max_lines=int_arg(optarg, 0, 1000000, 1)) == INVI) {
          eexit(EC_ARG, "Invalid maximum lines to receive (0 to 1e6): %s", optarg);
        }
        debug("parse_args() max_lines=%d", max_lines);
        break;
      case 'w':
        if((character_wait=int_arg(optarg, 0, 100000000, 1e6)) == INVI) {
          eexit(EC_ARG, "Invalid maximum lines to receive (0 to 1e8): %s", optarg);
        }
        debug("parse_args() character_wait=%ld", character_wait);
        break;
      case 'W':
        if((start_wait=int_arg(optarg, 0, 100000000, 1e6)) == INVI) {
          eexit(EC_ARG, "Invalid initial wait time (0 to 1e8): %s", optarg);
        }
        debug("parse_args() start_wait=%ld", character_wait);
        break;
      default :
        eexit(EC_ARG, "Invalid option %s", argv[optind-1]);
    }
  }
 
  if(optind<1) optind = 1;
  argv += optind;
  argc -= optind;
 
  for(n=0; n<argc && argv[n]; n++) {
    char *p, *pp; p = argv[n];
    switch(n) {
      case 0: // port file
        strncpy(portfile, p, sizeof(portfile));
        portfile[sizeof(portfile)-1] = '\0';
        debug("parse_args() portfile=%s", portfile);
        if(!strlen(portfile)) eexit(EC_ARG, "No port given (/dev/...)");
        break;
      case 1:
        // Format 115200N81
        for(pp=p; *pp; pp++) {
          if(!isdigit(*pp)) {
            if((parity = tolower(*pp)) != 'n' && parity != 'e' && parity != 'o') {
              eexit(EC_ARG, "Invalid parity ('n','e','o'): %c", *pp);
            }
            debug("parse_args() parity=%c", parity);
            (*pp) = '\0'; // limit string for baud rate later
            if(*++pp) {
              if((databits = ((*pp)-'0')) != 7 && databits != 8) {
                eexit(EC_ARG, "Invalid data bits (7 or 8): %c", *pp);
              }
              debug("parse_args() databits=%d", databits);
            }
            if(*++pp) {
              if((stopbits = ((*pp)-'0')) != 1 && stopbits != 2) {
                eexit(EC_ARG, "Invalid stop bits (1 or 2): %c", *pp);
              }
              debug("parse_args() stopbits=%d", stopbits);
            }
          }
        }
        if((baudrate = atoi(p)) <= 0) {
          eexit(EC_ARG, "Invalid baud rate: %s", p);
        } else {
          debug("parse_args() baudrate=%d", baudrate);
          int baudrate_ok = 0;
          const unsigned long* r;
          for(r=serial_port_get_valid_baudrates(); r&&*r; r++) {
            if(*r == baudrate) baudrate_ok = 1;
            if(baudrate < *r) break;
          }
          if(!baudrate_ok) {
            eexit(EC_ARG, "Invalid baud rate: %d (Did you mean %d)", baudrate,
                (r&&*r?*r:0));
          }
        }
        break;
      default:
        ;
    }
  }
}
 
 
/**
 * Signal handler, exits with the error code arg signum
 *
 * @param int signum
 * @return void
 */
void signal_handler(int signum)
{
  debug("signal_handler(%d)", signum);
  int code = EC_SIGNAL;
  const char *signal_s;
  switch(signum) {
    case SIGTERM: signal_s = "kill signal"; break;
    case SIGKILL: signal_s = "hard kill signal"; break;
    case SIGINT: signal_s = "CTRL-C"; code=0; break;
    case SIGHUP: signal_s = "hangup signal (SIGHUP)"; break;
    case SIGABRT: signal_s = "error in program (SIGABRT)"; break;
    case SIGTRAP: signal_s = "debug trap (TRAP)"; break;
    case SIGPIPE: signal_s = "broken pipe"; break;
    case SIGSYS: signal_s = "Program error (bad system call)"; break;
    default: signal_s = "(unknown signal)";
  }
  eexit(code, "Exit due to signal: %s", signal_s);
}
 
 
 
 
/**
 * Prints an error message and exits.
 *
 * @param err_msg
 * @param ...
 * @return void
 */
void eexit(int code, const char* err_msg, ...)
{
  int i;
  debug("eexit(%d, %s)", code, err_msg?err_msg:"(no message)");
 
  // Close and restore
  serial_port_close(&port);
 
  // Print last lines ...
  set_color(code!=0 ? COLOR_ERR : COLOR_OK, 1);
  if(is_verbose || code != 0) {
    if(!err_msg) {
      fprintf(stderr, "\n[Unexpected error]");
    } else {
      char buffer[256];
      va_list args;
      va_start (args, err_msg);
      vsnprintf (buffer, sizeof(buffer), err_msg, args);
      va_end (args);
      buffer[sizeof(buffer)-1] = '\0';
      if(err_msg) fprintf(stderr, "\n[%s]", buffer);
    }
    if(is_verbose) {
      fprintf(stderr, "[E%d]\n[TX:%llu, RX:%llu, RX:%llu lines]", code, bytes_tx,
          bytes_rx, lines_received);
    }
    fprintf(stderr, "\n");
  }
  debug("eexit() EXIT");
  set_color(COLOR_NO, 0);
  set_color(COLOR_NO, 1);
 
  if(in_d >= 0) {
    for(i=0; i<sizeof(stdios_orig); i++) {
      if(((unsigned char*)&stdios_orig)[i]) {
        tcsetattr(in_d, TCSANOW, &stdios_orig);
        break;
      }
    }
    close(in_d);
    in_d = -1;
  }
  if(out_d >= 0) {
    close(out_d);
    out_d = -1;
  }
 
  exit(code);
}
 
 
 
int cycle(void);
 
/**
 * Main
 *
 * @param int argc
 * @param const char** argv
 * @return int
 */
int main(int argc, char** argv)
{
  // Init
  in_d = fileno(stdin);
  out_d = fileno(stdout);
  setlocale(LC_ALL, "");
 
  debug("main() Setup signals");
  // Signals
  signal(SIGTERM, signal_handler);
  signal(SIGKILL, signal_handler);
  signal(SIGINT, signal_handler);
  signal(SIGHUP, signal_handler);
  signal(SIGABRT, signal_handler);
  signal(SIGTRAP, signal_handler);
  signal(SIGPIPE, signal_handler);
  signal(SIGSYS, signal_handler);
 
  // INIT
  debug("main() Init variables");
  memset(&buffer, 0, sizeof(buffer)); buffer.sz = sizeof(buffer.s)-1;
  memset(disconnect_cmd, 0, sizeof(disconnect_cmd));
  memset(&stdios, 0, sizeof(stdios));
  memset(&stdios_orig, 0, sizeof(stdios_orig));
  memset(portfile, 0, sizeof(portfile));
  memset(infile, 0, sizeof(infile));
  memset(outfile, 0, sizeof(outfile));
  strcpy(infile, "/dev/stdin");
  strcpy(outfile, "/dev/stdout");
  parse_args(argc, argv);
 
  // Port settings
  debug("main() Init serial port");
  serial_port_init(&port);
  strncpy(port.file, portfile, sizeof(port.file));
  port.baudrate = baudrate;
  port.parity = parity;
  port.databits = databits;
  port.stopbits = stopbits;
  port.timeout_us = 1000;
  port.flush = 1;
 
  if(serial_port_open(&port) < 0) {
    eexit(EC_IO, port._state.error_string);
  }
 
  if(is_debug) {
    set_color(COLOR_DBG,1);
    serial_port_dump(&port);
    //set_color(COLOR_NO,1);
  }
 
  // Local TTY / files
  if(!strcmp(infile, "/dev/stdin")) {
    debug("main() Local input is STDIN");
    in_d = fileno(stdin);
  } else if((in_d=open(infile, O_RDONLY)) < 0) {
    eexit(EC_IO, "Failed to open input");
  }
  if(!strcmp(outfile, "/dev/stdout")) {
    debug("main() Local input is STDOUT");
    out_d = fileno(stdout);
  } else if((out_d=open(outfile, O_WRONLY)) < 0) {
    eexit(EC_IO, "Failed to open output");
  }
 
  fcntl(in_d, F_SETFL, O_NONBLOCK);
  fcntl(out_d, F_SETFL, O_NONBLOCK);
 
  if(isatty(in_d)) {
    in_is_tty = 1;
    tcgetattr(in_d, &stdios_orig);
    stdios = stdios_orig;
    stdios.c_lflag &= ~(ECHO|ICANON);
    stdios.c_cc[VTIME] = 0;
    stdios.c_cc[VMIN] = 1; // stfwi: double check
    debug("main() Local input is TTY");
  } else {
    in_is_tty = 0;
    debug("main() Local input is NO TTY");
  }
  tcsetattr(in_d, TCSANOW, &stdios);
 
  if(isatty(out_d)) {
    out_is_tty = 1;
    debug("main() Local output is TTY");
  } else {
    out_is_tty = 0;
    debug("main() Local output is NO TTY");
  }
 
  // Prepare
  debug("main() Prepare run");
  timeout = timeout_cfg;
  debug("main() Initial timeout: %f", timeout);
  was_newline = 1;
  lines_received = 0;
  bytes_tx = 0;
  bytes_tx = 0;
  next_char_wait_count = 0;
  disconnect_cmd_len = strlen(disconnect_cmd);
 
  if(start_wait > 0) {
    usleep(start_wait);
  }
 
  // RUN, abort with signals
  debug("main() Run ...");
  while(cycle() == 0);
  debug("main() ... run break.");
  eexit(0, "Bye");
  return 0;
}
 
/**
 * Called in a loop. Returns nonzero if the loop shall exit
 * @return int
 */
int cycle(void)
{
  fd_set rfds, wfds,efds;
  struct timeval tv;
  char s[256];
  int n,i;
 
  // Wait for events
  tv.tv_sec=0; tv.tv_usec=1000;
  FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); n=0;
 
  if(timeout_cfg>0 && (timeout-= ((double)tv.tv_usec/(double)1000000))<=0) {
    eexit(0, "Timed out after %.1fs", timeout_cfg);
  }
 
  if(next_char_wait_count > 0) {
    next_char_wait_count -= tv.tv_usec;
  } else {
    if(!in_eof) {
      FD_SET(in_d, &rfds); n++;
    }
  }
 
  FD_SET(in_d, &efds); n++;
  if(out_is_tty) FD_SET(out_d, &efds); n++;
  FD_SET(port._state.handle, &efds); n++;
  FD_SET(port._state.handle, &rfds); n++;
 
  if((n=select((int)n+1, &rfds, &wfds, &efds, &tv)) < 0) switch(n) {
    case EBADF: eexit(EC_FATAL, "An invalid file descriptor was given in one of the sets"); break;
    case EINTR: debug("EINTR"); break; // A signal was caught
    case EINVAL: eexit(EC_FATAL, "Invalid file descriptor invalid timeout"); break;
    case ENOMEM: eexit(EC_FATAL, "Unable to allocate memory for internal select() tables"); break;
    default: eexit(EC_FATAL, "Waiting for i/o states failed (select()<0): %s", strerror(errno));
  }
 
  if(!n) {
    //return 0;
  } else {
    timeout = timeout_cfg;
  }
 
  //debug("[cycle(timeout=%.1f)]", timeout);
 
  if(FD_ISSET(out_d, &efds) || FD_ISSET(in_d, &efds) || FD_ISSET(port._state.handle, &efds)) {
    if(FD_ISSET(out_d, &efds)) eexit(EC_IO, "I/O local out error: %s", strerror(errno));
    if(FD_ISSET(in_d, &efds)) eexit(EC_IO, "I/O local in error: %s", strerror(errno));
    if(FD_ISSET(port._state.handle, &efds)) eexit(EC_IO, "I/O port error: %s", strerror(errno));
  }
 
  // Serial --> stdout
  if(FD_ISSET(port._state.handle, &rfds)) {
    if((n=serial_port_read(&port, s, sizeof(s)-1)) < 0) {
      eexit(EC_IO, port._state.error_string);
    }
 
    if(n > 0) {
      debug("[RX %d]", n);
      bytes_rx += n;
      set_color(COLOR_RX, 0);
      for(i=0; i<n; i++) {
        if(s[i] == '\r' || s[i] == '\n') {
          was_newline = 1;
          if(s[i] == '\r' || !was_cr) lines_received++;
          was_cr = (s[i] == '\r');
          if((lines_received >= max_lines) && max_lines>0) {
            if(write(out_d, "\n", 1) < 0) { // we add this to get a new line in the terminal
              eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
            }
            eexit(0, "Specified number of lines received");
          }
        } else if(was_newline) {
          was_newline = 0;
          if(add_timestamps) {
            char ts[64];
            struct timeval tod;
            gettimeofday(&tod, NULL);
            snprintf(ts, 64, "%.2f ", (double) time(NULL) + (double) tod.tv_usec * 1e-6);
            set_color(COLOR_TS, 0);
            if(write(out_d, ts, strlen(ts)) < 0) {
              eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
            }
            set_color(COLOR_RX, 0);
          }
        }
        print_nonprintable(s[i]);
        if(((!nonprintable) || (s[i]!='\r')) && (write(out_d, s+i, 1) < 0)) {
          eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
        }
      }
      //set_color(COLOR_NO, 0);
 
      // Disconnect command
      if(disconnect_cmd_len > 0) {
        int k, l;
        for(i=0; i<n; i++) {
          buffer.s[buffer.p] = s[i];
          if(++buffer.p >= buffer.sz) buffer.p = 0;
          if((k = buffer.p - disconnect_cmd_len)<0) k+= buffer.sz;
          l = 0;
          while(k != buffer.p) {
            if(buffer.s[k++] != disconnect_cmd[l++]) break;
            if(k >= buffer.sz) k = 0;
            if(l == disconnect_cmd_len) {
              debug("DETECTED '%s'", disconnect_cmd);
              eexit(0, "Exit due to disconnect command");
            }
          }
        }
      }
    }
  }
 
  // stdin --> Serial
  if(FD_ISSET(in_d, &rfds)) {
    next_char_wait_count = character_wait;
    if((n=read(in_d, s, character_wait>0 ? 1 : (sizeof(s)-1))) < 0 && errno != EWOULDBLOCK) {
      eexit(EC_IO, "Reading local input failed: %s", strerror(errno));
    }
    if(n > 0) {
      //s[n+1] = '\0';
      debug("[TX %d]", n);
      if(serial_port_write(&port, s, n) < 0) {
        eexit(EC_IO, port._state.error_string);
      }
      bytes_tx += n;
      if(is_local_echo) {
        set_color(COLOR_TX, 0);
        for(i=0; i<n; i++) {
          if(nonprintable && s[i] < 16) print_nonprintable(s[i]);
          if(!nonprintable || s[i]!='\r') {
            if(write(out_d, s+i, 1) < 0) {
              eexit(EC_IO, "Writing to local output failed: %s", strerror(errno));
            }
          }
        }
        //set_color(COLOR_NO, 0);
      }
    } else if(n==0 && !in_is_tty) {
      in_eof = 1;
      debug("[EOF IN]");
    }
  }
  return 0;
}

Serial port handling files

Behandlung der seriellen Schnittstelle

/**
 * Serial port interface
 *
 * Usage:
 *  serial_port_t port;
 *  serial_port_init(&port);
 *  strncpy(port.file, "/dev/ttyXXX", sizeof(port.file));
 *  port.baudrate = 115200;     or other valid rates
 *  port.parity = 'n';          or , 'e', or 'o'
 *  port.databits = 8           or 7 or 6 or 5
 *  port.stopbits = 1;          or 2
 *  port.read_timeout_us=1000;  or other timeouts or -1 for "read blocking"
 *  port.local = 1;             or 0 or -1 for 'don't change'.
 *  port.set_dtr = -1;          or 0 or 1. -1== don't change
 *  port.set_rts = -1;          or 0 or 1. -1== don't change
 *
 *  Meaning of port.local: "Ignore modem status lines"
 *  Meaning of set_dtr   : "Set DTR directly when opening"
 *  Meaning of set_rts   : "Set RTS directly when opening"
 *
 * @file serialport.h
 * @author stfwi
 * @license Free, non-/commercial use granted.
 */
#ifndef SERIALPORT_H
#define SERIALPORT_H
 
#include <termios.h>
#ifdef  __cplusplus
extern "C" {
#endif
 
/**
 * Errors returned by serial port functions
 */
typedef enum {
  E_SERIAL_OK = 0,
  E_SERIAL_NULLPOINTER = -1,
  E_SERIAL_OPEN = -2,
  E_SERIAL_NOTTY = -3,
  E_SERIAL_GETATTR = -4,
  E_SERIAL_SETATTR = -5,
  E_SERIAL_GETIOC = -6,
  E_SERIAL_SETIOC = -7,
  E_SERIAL_SETNONBLOCK = -8,
  E_SERIAL_SELECT = -9,
  E_SERIAL_READ = -10,
  E_SERIAL_BAUDRATE = -11,
  E_SERIAL_IOCTL = - 12,
  E_SERIAL_WRITE = -13
} serial_port_error_t;
 
 
/**
 * Port definition and state structure.
 */
typedef struct {
  char file[128];                   /* Port file to open, e.g. /dev/ttyUSB      */
  int  baudrate;                    /* A valid baud rate                        */
  char parity;                      /* Parity, either 'n', 'e', or 'o'          */
  char stopbits;                    /* Stop bits: 0=1bit, 1=2 bits              */
  char databits;                    /* Data bits 5 to 8                         */
  char local;                       /* 1= modem status lines are ignored        */
  char flush;                       /* 1= clear buffers after opening           */
  char set_dtr;                     /* Init DTR, 0=clear, 1=set, -1=dont change */
  char set_rts;                     /* Init RTS, 0=clear, 1=set, -1=dont change */
  long long timeout_us;             /* <0=read blocking, >=0: timeout in us     */
  struct {                          /* State structure (dynamically changed)    */
    serial_port_error_t error_code; /* Last error code                          */
    char error_string[128];         /* Last error string                        */
    int handle;                     /* Handle of the open port file             */
    struct termios attr;            /* Applied terminal io attributes           */
    struct termios attr_original;   /* Terminal io attributes before opening    */
    int mdlns;                      /* Modem line states                        */
    int mdlns_original;             /* Modem line states before opening         */
  } _state;
} serial_port_t;
 
 
 
/**
 * Returns a list of valid baudrates. The end of the list is marked with the
 * value 0
 *
 * @return const int*
 */
const unsigned long* serial_port_get_valid_baudrates(void);
 
/**
 * Initialises the structure variable, resets all settings and the state to
 * 0 or the defaults, respectively.
 * @param serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_init(serial_port_t *port);
 
 
/**
 * Opens and applies the settings defined in port. Returns 0 on success,
 * or another serial_port_error_t.
 *
 * @param serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_open(serial_port_t *port);
 
 
/**
 * Close the port
 *
 * serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_close(serial_port_t *port);
 
/**
 * Wait until a port input/output/error flag is set, timout is port.timeout_us.
 * Arguments 'r', 'w' and 'e' are boolean flags what events shall be checked
 * (r=have received, w=send buffer empty, e=error somewhere).
 *
 * Return value: bit0: r is set, bit1: w is set, bit2: e is set.
 *
 * @param serial_port_t *port
 * @param int r
 * @param int w
 * @param int e
 * @return int
 */
int serial_port_wait_for_events(serial_port_t *port, int r, int w, int e);
 
/**
 * Read from the port. Returns the number of bytes read or,
 * if <0 a serial_port_error_t.
 *
 * @param serial_port_t *port
 * @param char* buffer
 * @param unsigned sz
 * @return int
 */
int serial_port_read(serial_port_t *port, char* buffer, unsigned sz);
 
/**
 * Write sz bytes. Returns number of bytes written or <0 on error
 *
 * @param serial_port_t *port
 * @param const char* buffer
 * @param unsigned sz
 * @return int
 */
int serial_port_write(serial_port_t *port, const char* buffer, unsigned sz);
 
/**
 * Clears RX and TX buffers ("flush"). It wait_until_all_output_sent==1, waits
 * until TX buffer is completely sent.
 * @param serial_port_t *port
 * @param int wait_until_all_output_sent
 */
int serial_port_clear_buffers(serial_port_t *port, int wait_until_all_output_sent);
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_cts(serial_port_t *port);
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_dsr(serial_port_t *port);
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_dtr(serial_port_t *port);
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_rts(serial_port_t *port);
 
/**
 * Returns 0 on success <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_set_dtr(serial_port_t *port, int DTR);
 
/**
 * Returns 0 on success <0 on error
 *
 * @param serial_port_t *port
 * @return int
 */
int serial_port_set_rts(serial_port_t *port, int RTS);
 
/**
 * Print port settings and state to stderr.
 * @param serial_port_t *port
 */
void serial_port_dump(serial_port_t *port);
 
#ifdef  __cplusplus
}
#endif
#endif
/**
 * Serial port interface
 *
 * @file serialport.c
 * @author stfwi
 * @license Free, non-/commercial use granted.
 */
#include "serialport.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#ifdef __linux__
#include <sys/file.h>
#endif
 
/* Error helper macro */
#define ERR_S_NULLPOINTER "Null pointer given"
#define seterror(CODE, ...) {\
  port->_state.error_code = CODE; \
  snprintf(port->_state.error_string, sizeof(port->_state.error_string), __VA_ARGS__); \
  if(CODE!=0) return CODE; \
}
 
/* Used fields in port pointer */
#define _hnd (port->_state.handle)
#define _file (port->file)
#define _attr (port->_state.attr)
#define _attr_orig (port->_state.attr_original)
#define _mdlns (port->_state.mdlns)
#define _mdlns_orig (port->_state.mdlns_original)
#define _baudrate (port->baudrate)
#define _parity (port->parity)
#define _databits (port->databits)
#define _stopbits (port->stopbits)
#define _ign_ms (port->local)
#define _rts (port->set_rts)
#define _dtr (port->set_dtr)
#define _timeout_us (port->timeout_us)
#define _flush (port->flush)
#define _errc (port->_state.error_code)
#define _errs (port->_state.error_string)
 
 
/**
 * Allowed baud rates
 */
static const unsigned long serial_port_baudrates[] = {
  50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
  #ifdef B7200
  7200,
  #endif
  9600, 19200, 38400,
  #ifdef B14400
  14400,
  #endif
  #ifdef B28800
  28800,
  #endif
  57600,
  #ifdef B76800
  76800,
  #endif
  115200, 230400, 0
};
 
/**
 * Returns a list of valid baudrates. The end of the list is marked with the
 * value 0
 *
 * @return const int*
 */
const unsigned long* serial_port_get_valid_baudrates(void)
{
  return serial_port_baudrates;
}
 
/**
 * Initialises the structure variable, resets all settings and the state to
 * 0 or the defaults, respectively.
 * @param serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_init(serial_port_t *port)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  memset(port, 0, sizeof(serial_port_t));
  port->baudrate = 9600;
  port->parity = 'n';
  port->databits = 8;
  port->stopbits = 1;
  port->local = 1;
  port->timeout_us = -1;
  port->set_dtr = -1;
  port->set_rts = -1;
  return E_SERIAL_OK;
}
 
/**
 * Opens and applies the settings defined in port. Returns 0 on success,
 * or another serial_port_error_t.
 * @param serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_open(serial_port_t *port)
{
  int n;
  if(!port) {
    seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  } else if((_hnd=open(_file, O_RDWR|O_NDELAY|O_NOCTTY)) < 0) {
    if(strstr(strerror(errno), "denied")) {
      seterror(E_SERIAL_OPEN, "Failed to open port '%s' (NOTE: Are you in group 'dialout'?): %s.", _file, strerror(errno))
    } else {
      seterror(E_SERIAL_OPEN, "Failed to open port '%s': %s.", _file, strerror(errno))
    }
  } else if(flock(_hnd, LOCK_EX) < 0) {
    seterror(E_SERIAL_OPEN, "Port is already in use (didn't get exclusive lock): %s (Error=%s)", _file, strerror(errno))
  } else if(!isatty(_hnd)) {
    seterror(E_SERIAL_NOTTY, "Port is no TTY: %s]\n\n", _file);
  } else if(tcgetattr(_hnd, &_attr_orig) < 0) {
    seterror(E_SERIAL_GETATTR, "Failed to get port terminal attributes: %s", strerror(errno));
  } else if(ioctl(_hnd, TIOCMGET, &_mdlns_orig) < 0) {
    seterror(E_SERIAL_GETIOC, "Failed to get port io control: %s", strerror(errno));
  }
  _attr = _attr_orig;
  _mdlns = _mdlns_orig;
  _attr.c_iflag &= ~(IGNBRK|BRKINT|ICRNL|INLCR|PARMRK|IXON);
  _attr.c_cflag &= ~(ECHO|ECHONL|ICANON|IEXTEN|ISIG|ISTRIP|CRTSCTS);
  _attr.c_oflag &= ~(OCRNL|ONLCR|ONLRET|ONOCR|OFILL|OPOST);
  _attr.c_iflag = (_attr.c_iflag & ~(IGNPAR|INPCK)) | (_parity == 'n' ? IGNPAR : INPCK);
  _attr.c_cflag = (_attr.c_cflag & ~CSTOPB)| (_stopbits > 1 ? CSTOPB : 0);
  _attr.c_cflag = (_attr.c_cflag & ~(HUPCL|CLOCAL)) | CREAD | (_ign_ms ? CLOCAL : 0);
  _attr.c_cflag = (_attr.c_cflag & ~(PARENB))| (_parity != 'n' ? PARENB : 0);
  _attr.c_cflag = (_attr.c_cflag & ~(PARODD))| (_parity == 'o' ? PARODD : 0);
  switch(_databits) {
    case 5: n = CS5; break;
    case 6: n = CS6; break;
    case 7: n = CS7; break;
    case 8: n = CS8; break;
    default: n = CS8;
  }
  _attr.c_cflag = (_attr.c_cflag & ~CSIZE) | n;
  _attr.c_cc[VTIME] = 5;
  _attr.c_cc[VMIN]  = 1;
 
  speed_t br = B9600;
  switch(_baudrate) {
    case 50: br = B50; break;
    case 75: br = B75; break;
    case 110: br = B110; break;
    case 134: br = B134; break;
    case 150: br = B150; break;
    case 200: br = B200; break;
    case 300: br = B300; break;
    case 600: br = B600; break;
    case 1200: br = B1200; break;
    case 1800: br = B1800; break;
    case 2400: br = B2400; break;
    case 4800: br = B4800; break;
    #ifdef B7200
    case 7200: br = B7200; break;
    #endif
    case 9600: br = B9600; break;
    case 19200: br = B19200; break;
    case 38400: br = B38400; break;
    #ifdef B14400
    case 14400: br = B14400; break;
    #endif
    #ifdef B28800
    case 28800: br = B28800; break;
    #endif
    case 57600: br = B57600; break;
    #ifdef B76800
    case 76800: br = B76800; break;
    #endif
    case 115200: br = B115200; break;
    case 230400: br = B230400; break;
    case 0:
    default:
      seterror(E_SERIAL_SETATTR, "Invalid baud rate.");
  }
 
  cfsetispeed(&_attr, br);
  cfsetospeed(&_attr, br);
 
  if(_rts >=0) _mdlns = (_mdlns & ~(TIOCM_RTS)) | (_rts ? TIOCM_RTS : 0);
  if(_dtr >=0) _mdlns = (_mdlns & ~(TIOCM_DTR)) | (_dtr ? TIOCM_DTR : 0);
 
  cfmakeraw(&_attr);
 
  if(tcsetattr(_hnd, _flush ? TCSAFLUSH : TCSANOW, &_attr) < 0) {
    seterror(E_SERIAL_SETATTR, "Failed to set port terminal attributes.");
  } else if(ioctl(_hnd, TIOCMSET, &_mdlns) < 0) {
    seterror(E_SERIAL_SETIOC, "Failed to set port terminal attributes.");
  } else if((_timeout_us >=0) && (fcntl(_hnd, F_SETFL, O_NONBLOCK) < 0)) {
    seterror(E_SERIAL_SETNONBLOCK, "Failed to set port nonblocking.");
  }
 
  if(_flush) tcflush(_hnd, TCIOFLUSH);
  return E_SERIAL_OK;
}
 
/**
 * Close the port
 * serial_port_t *port
 * @return serial_port_error_t
 */
serial_port_error_t serial_port_close(serial_port_t *port)
{
  int i;
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  tcflush(_hnd, TCIOFLUSH);
  if(port->_state.handle < 0) return E_SERIAL_OK;
  for(i=0; i<sizeof((port->_state.attr_original)); i++) {
    if(((unsigned char*)&(port->_state.attr_original))[i]) {
      tcsetattr(port->_state.handle, TCSANOW, &(port->_state.attr_original));
      ioctl(_hnd, TIOCMSET, &_mdlns_orig);
      break;
    }
  }
  close(port->_state.handle);
  port->_state.handle = -1;
  return E_SERIAL_OK;
}
 
 
/**
 * Read from the port. Returns the number of bytes read or,
 * if <0 a serial_port_error_t.
 *
 * @param serial_port_t *port
 * @param char* buffer
 * @param unsigned sz
 * @return int
 */
int serial_port_read(serial_port_t *port, char* buffer, unsigned sz)
{
  if(!port || !buffer) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  long n = 0;
  if((n=read(_hnd, buffer, sz)) >= 0) {
    return n;
  } else if(errno == EWOULDBLOCK) {
    return 0;
  } else {
    seterror(E_SERIAL_READ, "Reading port failed: %s", strerror(errno));
  }
}
 
/**
 * Waits until something was received, all sent or an error occurred. The timeout
 * it specified using "port.timeout_us".
 * @param serial_port_t *port
 * @return
 */
int serial_port_wait_for_events(serial_port_t *port, int r, int w, int e)
{
  fd_set rfds, wfds,efds; struct timeval tv;
  long n = 1;
 
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds);
 
  if(_timeout_us >=0) {
    tv.tv_sec  = _timeout_us / 1000000;
    tv.tv_usec = _timeout_us % 1000000;
  } else {
    tv.tv_sec  = _timeout_us = 0; // Just return immediately if something's there
    tv.tv_usec = _timeout_us = 0;
  }
 
  if(r) { FD_SET(_hnd, &rfds); n++; }
  if(w) { FD_SET(_hnd, &wfds); n++; }
  if(e) { FD_SET(_hnd, &efds); n++; }
 
  if((n=select(n, &rfds, &wfds, &efds, &tv)) < 0) {
    switch(n) {
      case EWOULDBLOCK: return 0;
      case EBADF: seterror(E_SERIAL_SELECT, "An invalid file descriptor was given in one of the sets"); break;
      case EINTR: return E_SERIAL_OK; // A signal was caught
      case EINVAL: seterror(E_SERIAL_SELECT, "Invalid file descriptor invalid timeout"); break;
      case ENOMEM: seterror(E_SERIAL_SELECT, "Unable to allocate memory for internal select() tables"); break;
      default: seterror(E_SERIAL_SELECT, "Waiting for i/o states failed (select()<0): %s", strerror(errno));
    }
  } else if(!n) {
    return 0;
  } else {
    return ((FD_ISSET(_hnd, &rfds)?1:0)<<0) | ((FD_ISSET(_hnd, &wfds)?1:0)<<1) | ((FD_ISSET(_hnd, &efds)?1:0)<<2);
  }
}
 
/**
 * Write sz bytes. Returns number of bytes written or <0 on error
 * @param serial_port_t *port
 * @param const char* buffer
 * @param unsigned sz
 * @return int
 */
int serial_port_write(serial_port_t *port, const char* buffer, unsigned sz)
{
  unsigned int n;
  if(!port || !buffer) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  if((n=write(_hnd, buffer, sz) < 0)) {
    seterror(E_SERIAL_WRITE, "Failed write to port: %s", strerror(errno));
  }
  return n;
}
 
/**
 * Clears RX and TX buffers ("flush"). It wait_until_all_output_sent==1, waits
 * until TX buffer is completely sent.
 * @param serial_port_t *port
 * @param int wait_until_all_output_sent
 */
int serial_port_clear_buffers(serial_port_t *port, int wait_until_all_output_sent)
{
  tcflush(_hnd, TCIFLUSH);
  if(wait_until_all_output_sent) tcdrain(_hnd);
  tcflush(_hnd, TCIOFLUSH);
  return E_SERIAL_OK;
}
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_cts(serial_port_t *port)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for CTS: %s", strerror(errno));
  } else {
    return (m & TIOCM_CTS) ? 1 : 0;
  }
}
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_dsr(serial_port_t *port)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for DSR: %s", strerror(errno));
  } else {
    return (m & TIOCM_DSR) ? 1 : 0;
  }
}
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_dtr(serial_port_t *port)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for DTR: %s", strerror(errno));
  } else {
    return (m & TIOCM_DTR) ? 1 : 0;
  }
}
 
/**
 * Returns 1 if set, 0 if not set <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_get_rts(serial_port_t *port)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for RTS: %s", strerror(errno));
  } else {
    return (m & TIOCM_RTS) ? 1 : 0;
  }
}
 
/**
 * Returns 0 on success <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_set_dtr(serial_port_t *port, int DTR)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for set DTR: %s", strerror(errno));
  }
  m = (m & ~TIOCM_DTR) | (DTR ? TIOCM_DTR : 0);
  if(ioctl(_hnd, TIOCMSET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for set DTR: %s", strerror(errno));
  }
  return 0;
}
 
/**
 * Returns 0 on success <0 on error
 * @param serial_port_t *port
 * @return int
 */
int serial_port_set_rts(serial_port_t *port, int RTS)
{
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  int m;
  if(ioctl(_hnd, TIOCMGET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for set RTS: %s", strerror(errno));
  }
  m = (m & ~TIOCM_RTS) | (RTS ? TIOCM_RTS : 0);
  if(ioctl(_hnd, TIOCMSET, &m) < 0) {
    seterror(E_SERIAL_IOCTL, "ioctl() failed for set RTS: %s", strerror(errno));
  }
  return 0;
}
 
 
void serial_port_dump(serial_port_t *port)
{
  fprintf(stderr,
      "serial_port = {\n"
      "  file           = %s\n"
      "  baudrate       = %d\n"
      "  parity         = %c\n"
      "  databits       = %d\n"
      "  stopbits       = %d\n"
      "  local          = %d\n"
      "  flush          = %d\n"
      "  set_dtr        = %d\n"
      "  set_rts        = %d\n"
      "  timeout_us     = %lld\n"
      "  _state = {\n"
      "    error_code   = %d\n"
      "    error_string = %s\n"
      "    handle       = %d\n"
      "    mdlns        = %08xh\n"
      "    mdlns_orig   = %08xh\n"
      "    attr = {\n"
      "      c_iflag    = %08xh\n"
      "      c_oflag    = %08xh\n"
      "      c_cflag    = %08xh\n"
      "      c_lflag    = %08xh\n"
      "      c_cc       = (string)\n"
      "      c_ispeed   = %d\n"
      "      c_ospeed   = %d\n"
      "    }\n"
      "    attr_orig = {\n"
      "      c_iflag    = %08xh\n"
      "      c_oflag    = %08xh\n"
      "      c_cflag    = %08xh\n"
      "      c_lflag    = %08xh\n"
      "      c_cc       = (string)\n"
      "      c_ispeed   = %d\n"
      "      c_ospeed   = %d\n"
      "    }\n"
      "  }\n"
      "};\n",
      _file, _baudrate, _parity, _databits, _stopbits, _ign_ms, _flush, _dtr,
      _rts, _timeout_us, _errc, _errs, _hnd, _mdlns, _mdlns_orig,
      (unsigned)_attr.c_iflag, (unsigned)_attr.c_oflag, (unsigned)_attr.c_cflag,
      (unsigned)_attr.c_lflag, (unsigned)_attr.c_ispeed, (unsigned)_attr.c_ospeed,
      (unsigned)_attr_orig.c_iflag, (unsigned)_attr_orig.c_oflag, (unsigned)_attr_orig.c_cflag,
      (unsigned)_attr_orig.c_lflag, (unsigned)_attr_orig.c_ispeed, (unsigned)_attr_orig.c_ospeed
    );
}

Makefile

run:OPTIONS= -vnetD -T2 -l2
run:PORTFILE=$(shell ls -1 /dev/cu.usbmodemf* | head -1)
run:BAUD=115200
run:PARITY=N
run:DATABITS=8
run:STOPBITS=1
 
all: serialport
 
.PHONY: run
run: serialport
    -@./serialport $(OPTIONS) $(PORTFILE) $(BAUD)$(PARITY)$(DATABITS)$(STOPBITS) 1>&2
 
.PHONY: clean
clean:
    -@echo ""
 
serialport: serialport.c main.c
    @gcc -Wall -O3 serialport.c main.c -lm -o serialport

README

Linux/MacOSX/BSD shell program for simple serial port communication (containing
features for micro controller "release-version-debugging" / logging).

LICENSE

  main.c, vc920.c, vc920.h: GPL3.

  serialport.c/h: Free, non-commercial/commercial use granted.

AUTHOR

  Stefan Wilhelm (stfwi)

USAGE / FEATURES

  (For short overview and long option names see USAGE below).

  - Synopsis: serialport [options] <port file> [<port settings>]

  - Port settings are defined after the port (last argument), formatted as one
    word containing baud rate, parity, data bits and stop bits. E.g.
    9600N71 (9600bps no parity, 7 data bits, 1 stop bit). Default is 115200N81.
    All ommitted settings are replaced by the defaults, so "9600" is identical
    to "9600N81". Ranges:

      - Baudrate: A valid baud rate
      - Parity: N=none, O=odd, E=even. Lowercase letters are allowed.
      - Data bits: 5,6,7,8
      - Stop bits: 1,2

  - Input data/text can be either interactively typed or piped in, e.g.

      - `$ echo -e 'Hello!\n' | serialport /dev/ttyUSB0`

      - `$ cat input.txt | serialport /dev/ttyUSB0`

  - You exit the program with CTRL-C.

    - Additionally you can define a text that is detected and forces the program
      to quit. The option for this is `-d <STRING>` E.g. `serialport -d 'BYE\n' /dev/ttyS`.

    - Another way to exit is to define an inactivity timeout using `-T <FLOAT>`
      in seconds (floating point), e.g. `serialport -T 0.5 /dev/ttyS`.

    - The third way is to define a number of lines that shall be received. After
      this the program quits. The options is `-l <UINT>`. Newlines are detected
      by <CR> (\r), <LF> (\n) or <CR><LF>, but not <LF><CR>. E.g.
      `serialport -l 10 /dev/ttyS`.

    It does not matter if you piped in data or type interactively, both conditions
    cause the program to exit.

  - Normally the program does not echo what you typed or piped. To enable local
    echo set the `-e` option. Received data and local echo is printed to STDOUT,
    debug, stats and errors to STDERR.

  - To differ ok-status, errors, sent, received and debug text, the output can
    be coloured using the `-c` option. Errors are RED, ok GREEN, received BLUE,
    sent AQUA, debug text LIGHT GRAY.

  - If you like to see control characters as UTF-8 representations, enable `-n`
    (nonprintable).

  - To get statistics on quit, such as the number of bytes sent/received, as well
    as the exit code, increase the verbosity with `-v`. This will tell you why
    the program did exit (timeout, lines received, signal, error ...). To get
    detailed communication logs enable the debug mode `-D`.

  - To add timestamps (Unix timestamp + digits sub-second accuracy) before each
    line add `-t` to the option list.

  - As some controllers have trouble receiving at high baud rates, but can send
    at latter, you can specify a send-delay in seconds for each character/byte
    using `-w <FLOAT>`, e.g. `serialport -w0.01 ..` for 10ms. The program sill
    receives at full speed and the baud rate is untouched.

  - As some USB-serial converters cause some delay when initially configuring
    the port you can specify an initial wait time after opening / setting up
    the port and starting the communication. Use the `-W <FLOAT>` option for this.
    Unit is seconds, and fractions of a second are allowed as well, e.g.
    `serialport -W0.5 /dev/ttyUSB`.

USAGE:

  serialport [options] <port file> [BAUD=115200[PARITY=n[DATABITS=8[STOPBITS=1]]]]

  Simple serial port TTY redirection program for (raw) microcontroller
  communication. Exit with CTRL-C.

  Options:

   -h, --help          Print this help
   -v, --verbose       Make the program more chatty (on stderr)
   -T, --timeout=N     Exit after nothing happened for N seconds
   -l, --lines=N       Exit after receiving N lines
   -d, --disconnect=S  Disconnect when receiving this (\n etc allowed)
   -t, --timestamp     Add timestamp before each line
   -e, --local-echo    Print the sent characters locally
   -w, --char-wait=N   Wait N seconds before sending the next byte
   -W, --start-wait=N  Wait N seconds after opening port before doing anything
   -c, --color         Print with colors
   -n, --control-chars Print non-printable control characters
   -D, --debug         Print debug messages and dumps (stderr)

  Examples:

   serialport /dev/ttyS               Connect with default values

   serialport /dev/ttyS 115200        Set baud rate, rest default

   serialport /dev/ttyS 9600N81       Set baud rate, parity, data bits, stop bits

   serialport /dev/ttyS -T 0.5        Exit after 0.5s inactivity

   serialport /dev/ttyS -d 'Bye\\r'   Exit when receiving 'Bye<CR>'

   serialport /dev/ttyS -vect         Interactive mode with colors, local
                                      echo and timestamps for received lines

   echo 'hello!' | serialport -W1.0 /dev/ttyS
                                      Wait 1s before sending the line 'hello!'

   cat cmds | serialport -t -l 10 -T 0.5 /dev/ttyS | grep 'data'
                                      Send a file, fetch max 10 lines, quit after
                                      idle port for 0.5s and pipe to grep to fetch
                                      lines containing the text 'data'

  Exit codes (for shell programming):

   0 = OK, no error
   1 = Wrong argument, you mistyped something or the program can't handle it.
   2 = Signal caught, e.g. CTRL-C, broken pipe, kill, etc.
   3 = Fatal error. Something went wrong internally (memory, bugs(?) etc).
   4 = I/O error, e.g. Disk full, USB-serial-converted disconnected ...

  (serialport v1.0, stfwi)