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.

VC920/VC940 DMM Datenerfassung für Linux/MacOSX

VC920/VC940 DMM serial reading program for Linux/MacOSX

A Linux/MacOSX/*nix shell data acquisition program for VC920/VC940 DMM. You can download it via GIT here, and say make.

  • The program can connect to the DMM via serial port / USB converter, read and interpret the raw data. It prints either standard floating point numbers or (enabled with -e engineering formatted numbers) to the console, file or pipe, each record one line. If the DMM display shows 0L, H etc the output is NaN, INF, or -INF, respectively.

  • It automatically recognises the measurement mode/function and adapts the output values correspondingly. You can set the -u option to add the unit.

  • Normally the program times out after port inactivity of 5s. However, you can disable this feature using the -T option (for long tune-in times).

  • You can add timestamps to each data record by setting the -t option.

  • To receive only N data sets, use the option -n <N> then the software will exit after having received N lines.

  • If you like to see the raw output of the serial port communication, set the -r option. You can pipe these data into a file for later processing.

  • If you get an error "Can't open port: Permission denied", check that you are in group dialout. Otherwise Linux systems may deny access to the port.

  • If you get timeout instead of data, check that you pushed the SEND button on the DMM

Ein Linux/MacOSX/*nix Konsolenprogramm, um das Digitalmultimeter VC920 bzw. VC940 von Voltcraft über die serielle Schnittstelle bzw. über einen USB-Konverter auszulesen.

Heruntergeladen kann es hier über GIT, danach einfach make sagen.

  • Das Programm liest die Rohdaten über die Schnittstelle (optional auch aus Datei oder Pipe), interpretiert diese und gibt pro Datum eine Zeile aus, welche eine Fließkommazahl enthäht. Die Zahl ist entweder eine standard Fließkommazahl oder mit dem flag -e "Engineering"-formatiert, d.h. die Zehnerpotenzen sind durch drei teilbar (entsprechend milli, mikro, kilo ...). Für nicht interpretierbare Werte oder "0L" auf dem DMM Display wird entsprechend Inf, -Inf oder NaN ausgegeben.

  • Es erkennt automatisch die Aktuell gewählte Messfunktion und den Wertebereich, die Einheit kann mit dem Kommandozeilenargument -u angezeigt werden.

  • Normalerweise bricht das Program ab, wenn für 5s kein Datum empfangen wurde. Da bei manchen Messungen diese Zeit überschritten werden kann ist es möglich dieses Timeout mit der Option -T abzuschalten.

  • Um nur N Zeilen zu empfangen kann das Argument -n <Anzahl Zeilen> verwendet werden.

  • Um die Rohdaten (z.B. in eine Datei) auszugeben kann das Flag -r hinzugeschaltet werden.

  • Beim Fehler "Can't open port: Permission denied" ist es wahrscheinlich, dass der Nutzer nicht in der Gruppe dialout ist. Linux-Systeme verweigern dann häufig den Zugriff.

  • Wenn statt Daten Timeouts kommen ist es wahrscheinich, dass der SEND-Knopf am DMM nicht gedrückt wurde. Das DMM zeigt SEND auf dem Display an wenn dies eingechaltet ist.

Examples

This is how the output of the program looks like for different arguments and DMM functions:

Beispiele

So sieht die Programmausgabe aus bei unterschiedlichen DMM-Funktionen und Kommandozeilenparametern:

$ ./dmm-vc920 --help
dmm-vc920 [options] [</dev/portfile>]

  -t      Add timestamp
  -u      Add unit
  -e      Engineering number format
  -n <N>  Retrieve <N> records
  -r      Don't process, output raw data
  -T      Disable timeout for long measurement tune-in times

  dmm-vc920 v1.0 (stfwi)

Resistor (10k potentiometer), no formatting, timestamps and units:

$ ./dmm-vc920 /dev/ttyUSB
234.5
258.1
1074
3238
5010
6510
7930
8560
9360
10090

Thermopile, engineering formatted:

$ ./dmm-vc920 -e /dev/ttyUSB
+030.400e+00
+032.100e+00
+033.300e+00

Thermopile with timestamps, engineering formatting and units:

$ ./dmm-vc920 -e -u -t -l 10 /dev/ttyUSB
1380526429 +021.700e+00 ºC
1380526430 +023.000e+00 ºC
1380526430 +027.400e+00 ºC

10K resistor:

$ ./dmm-vc920 -e -u /dev/ttyUSB
inf Ω           <<<<<<< Display "0L" (not connected)
inf Ω
+001.200e+00 Ω  <<<<<<< Connecting 10K potentioneter
+048.800e+00 Ω
+707.000e+00 Ω
+002.524e+03 Ω
+003.144e+03 Ω
+004.720e+03 Ω
+005.570e+03 Ω
+006.470e+03 Ω
+007.400e+03 Ω
+009.570e+03 Ω
+010.110e+03 Ω

Automation / integration in other programs

Automation / Integration in andere Programme

Integration in shell script (test if value <1K):

#!/bin/bash
VALUE=$(dmm-vc920 -n 1 /dev/ttyUSB)
IS_LOWER_1K=$(calc -q -- "$VALUE<1000")
echo $VALUE 'OHM:' $IS_LOWER_1K
 
# prints: 7710 OHM: 0
#         1026 OHM: 0
#         228.7 OHM: 1
# Note: exit code 0 means true 1 false

Perl (hopefully cryptic enough ...)

#!/usr/bin/perl
sub get_dmm{return(-e$_[0]&&!-d$_[0]&&int($_[1])>0)?`dmm-vc920 -n $_[1] $_[0]`:();}
print get_dmm("dmm-0.txt",10) . "\n";

Integration in PHP (10K poti)

<?
function get_dmm($port, $n) {
    if(is_file($port) || !is_numeric($n) || (($n = intval($n)) < 1)) return false;
    @ob_start();
    @system('dmm-vc920 -t -n ' . $n . ' ' . escapeshellarg($port), &$exit_code);
    $lines = @ob_get_clean();
    if($exit_code != 0) return false;
    $return = array();
    $ts_start = 0;
    foreach(explode("\n", trim($lines, "\n\r\t ")) as $line) {
        $line = explode(' ', $line);
        if(count($line) != 2 || !is_numeric($line[0])) return false;
        if($ts_start == 0) $ts_start = intval($line[0]);
        $return[] = array('t' => intval($line[0])-$ts_start, 'v' => doubleval($line[1]));
    }
    return $return;
}
 
print_r(get_dmm('/dev/ttyUSB', 3));
 
/**
RETURNS: Array
(
    [0] => Array
        (
            [t] => 0
            [v] => 0
        )
 
    [1] => Array
        (
            [t] => 0
            [v] => 393
        )
 
    [2] => Array
        (
            [t] => 1
            [v] => 10110
        )
 
)
*/

Sorce code

Quelltext

Main file

Main-Datei

/**
 * VC920 DMM serial data reader shell program
 *
 * @file main.c
 * @author stfwi
 * @license Free, non-/commercial use granted. The "@author stfwi" must remain
 *          in the head of the file and in the -h/--help output.
 */
#include "serialport.h"
#include "vc920.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
 
#define PRG_NAME ("dmm-vc920")
#define PRG_VERSION ("1.0")
 
serial_port_t port;
int add_timestamp = 0;
int read_n = -1;
int raw_data = 0;
int add_unit = 0;
int is_first_record = 1;
int fd = -1;
int timeout_counter = 0;
int timeout = 5000;
int format_eng = 0;
 
 
/**
 * Print help and exit
 */
void help(void)
{
  printf(
    "Usage: %s [options] [</dev/portfile>]\n\n"
    "       -t      Add timestamp\n"
    "       -u      Add unit\n"
    "       -e      Engineering number format\n"
    "       -n <N>  Retrieve <N> records\n"
    "       -r      Don't process, output raw data\n"
    "       -T      Disable timeout for long measurement tune-in times\n"
    "\n"
    "       %s. v%s (stfwi)\n"
    , PRG_NAME, PRG_NAME, PRG_VERSION
  );
  exit(1);
}
 
/**
 * Prints an error message and exits.
 *
 * @param err_msg
 * @param ...
 * @return void
 */
void exit_error(const char* err_msg, ...)
{
  if(fd < 0) {
    serial_port_close(&port);
  } else {
    close(fd);
  }
 
  if(!err_msg) {
    fprintf(stderr, "\n[Unexpected error]\n\n");
  } else {
    char buffer[256];
    va_list args;
    va_start (args, err_msg);
    vsnprintf (buffer, sizeof(buffer), err_msg, args);
    va_end (args);
    if(err_msg) fprintf(stderr, "\n[Exit due to error \"%s\"]\n\n", buffer);
  }
  exit(1);
}
 
/**
 * Analyse the DMM data
 *
 * @param const char* s
 * @return int
 */
int analyse_data(const char* s)
{
  const char *mode, *unit;
  unsigned long flags;
  double value = vc920_analyse(s, strlen(s), &unit, &mode, &flags);
 
  if(fd < 0 && raw_data) {
    if(write(STDOUT_FILENO, s, strlen(s)) <0) {
      exit_error("Write to tty/file/pipe failed: %s", strerror(errno));
    }
    return (read_n>=0 && read_n-- == 0) ? -1 : 0;
  }
  if(isnan(value) && is_first_record) return 0;
  if(read_n>=0 && read_n-- == 0) return -1;
  is_first_record = 0;
  if(add_timestamp) printf("%lu ", time(NULL));
  if(!format_eng || isinf(value) || isnan(value)) {
    printf("%g", value);
  } else {
    long dim = value!=0 ? floor(log10(value*(value<0?-1:1))) : 0;
    double v = value / pow(10, dim);
    while(dim % 3 != 0) { dim--; v*=10; }
    printf("%+08.3fe%+03d", v, (int)dim);
  }
  if(add_unit) {
    printf(" %s", unit);
  }
  printf("\n");
  return 0;
}
 
/**
 * Main
 *
 * @param int argc
 * @param const char** argv
 * @return int
 */
int main(int argc, const char** argv)
{
  char buffer[256];
  memset(buffer,0, sizeof(buffer));
  unsigned p = 0;
  int n = 0;
  char c = 0;
 
  // Args
  if(argc > 1) {
    if(argc < 2 || (!strcmp(argv[1], "--help")) || (!strcmp(argv[1], "-h"))) {
      help();
    } else {
      int i;
      for(i=1; argv[i]; i++) {
        if(!strlen(argv[i])) {
          continue;
        } else if(!strcmp(argv[i], "-t")) {
          add_timestamp = 1;
        } else if(!strcmp(argv[i], "-T")) {
          timeout = 0;
        } else if(!strcmp(argv[i], "-e")) {
          format_eng = 1;
        } else if(!strcmp(argv[i], "-r")) {
          raw_data = 1;
        } else if(!strcmp(argv[i], "-u")) {
          add_unit = 1;
        } else if(!strcmp(argv[i], "-n") || !strcmp(argv[i], "-l")) {
          if(!argv[i+1] || isnan(atof(argv[i+1]))) {
            exit_error("Argument '-n' requires a number as following argument");
          } else {
            read_n = atoi(argv[++i]);
          }
        } else if(argv[i][0] == '-') {
          exit_error("Unknown argument '%s'", argv[i]);
        } else {
          strncpy(buffer, argv[i], sizeof(buffer));
        }
      }
    }
  }
 
  // Check input source
  if(!strlen(buffer)) {
    if(isatty(STDIN_FILENO)) {
      help();
    } else {
      fd = STDIN_FILENO;
    }
  } else if((fd=open(buffer, O_RDONLY)) < 0) {
    fprintf(stderr, "Failed to open '%s': %s", buffer, strerror(errno));
    exit(1);
  } else if(isatty(fd)) {
    // Port operation
    close(fd);
    fd = -1;
    serial_port_init(&port);
    strncpy(port.file, buffer, sizeof(port.file));
    port.baudrate = 2400;
    port.parity = 'o';
    port.databits = 7;
    port.stopbits = 1;
    port.set_dtr = 1;
    port.set_rts = 0;
    port.flush = 1;
    port.timeout_us = 1000;
    timeout_counter = timeout;
    if(serial_port_open(&port) < 0) {
      exit_error(port._state.error_string);
    }
  }
 
  // Loop
  while(1) {
    if(fd < 0) {
      if(timeout && --timeout_counter <= 0) {
        exit_error("Timeout reading port (You can disable this using the '-T' option)");
      }
      if((n=serial_port_read(&port, &c, 1)) < 0) {
        exit_error(port._state.error_string);
      } else if(!n) {
        usleep(1000);
        continue;
      }
      timeout_counter = timeout;
    } else {
      if((n = read(fd, &c, 1)) < 0) {
        exit_error("Failed to read: %s", strerror(errno));
      } else if(!n) {
        break; // eof
      }
    }
 
    c &= 0x7f;
    buffer[p++] = c;
    if(c == '\n') {
      buffer[p] = '\0';
      if(analyse_data(buffer) < 0) {
        break;
      }
      p=0;
    } else if(p>=sizeof(buffer)) {
      exit_error("Read buffer overflow, no CRLF received.");
    }
  }
 
  if(fd < 0) {
    serial_port_close(&port);
  } else {
    close(fd);
  }
  return 0;
}

Data parser

Parser

/**
 * VC920 DMM data analyser
 *
 * @file vc920.h
 * @author stfwi
 * @license GPL3
 */
#ifndef VC920_H
#define VC920_H
#ifdef  __cplusplus
extern "C" {
#endif
 
#define VC930_FLAG_DC(flags) ((flags>>0) & 1u)
#define VC930_FLAG_AC(flags) ((flags>>1) & 1u)
#define VC930_FLAG_MANUAL(flags) ((flags>>2) & 1u)
#define VC930_FLAG_AUTO(flags) ((flags>>3) & 1u)
#define VC930_FLAG_NEGATIVE(flags) ((flags>>4) & 1u)
#define VC930_FLAG_RANGE(flags) ((flags>>5) & 3u)
#define VC930_FLAG_FUNCTION(flags) ((flags>>8) & 0xf)
 
double vc920_analyse(const char* s, unsigned sz, const char **unit, const char **mode, unsigned long *flags);
 
#ifdef  __cplusplus
}
#endif
#endif
/**
 * VC920 DMM data analyser
 *
 * @file vc920.c
 * @author stfwi
 * @license GPL3
 */
#include "vc920.h"
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
 
const char NO_UNIT[] = "";
const char NO_MODE[] = "";
const char data2digit[] = "0123456789 -LdeH";
 
#define n_digits (5)
 
 
const struct
{
  const char * name;
  const char * unit;
  double ranges[8];
}
functions[16] =
{
/* 0 */ { "Vac-mV"  , "V" , { 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05 } },
/* 1 */ { "Vdc-V"   , "V" , { 1e-05, 1e-04, 1e-03, 1e-02, 1e-01, 1e-00, 1e-00, 1e-00 } },
/* 2 */ { "Vac-V"   , "V" , { 1e-05, 1e-04, 1e-03, 1e-02, 1e-01, 1e-00, 1e-00, 1e-00 } },
/* 3 */ { "Vdc-mV"  , "V" , { 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05, 1e-05 } },
/* 4 */ { "R"       , "Ω" , { 1e+00, 1e-01, 1e+00, 1e+01, 1e+02, 1e+03, 1e+04, 1e+04 } },
/* 5 */ { "C"       , "F" , { 1e-12, 1e-12, 1e-11, 1e-10, 1e-09, 1e-08, 1e-07, 1e-06 } },
/* 6 */ { "T"       , "ºC", { 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01 } },
/* 7 */ { "I"       , "A" , { 1e-11, 1e-10, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09 } },
/* 8 */ { "I"       , "A" , { 1e-06, 1e-05, 1e-04, 1e-03, 1e-02, 1e-01, 1e-00, 1e-03 } },
/* 9 */ { "I"       , "A" , { 1e-03, 1e-03, 1e-03, 1e-03, 1e-03, 1e-03, 1e-03, 1e-03 } },
/* a */ { "R-beep"  , "Ω" , { 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01 } },
/* b */ { "V-diode" , "V" , { 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00 } },
/* d */ { "f"       , "Hz", { 1e-03, 1e-02, 1e-01, 1e+00, 1e+01, 1e+02, 1e+03, 1e+04 } },
/* d */ { "T"       , "ºF", { 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01, 1e-01 } },
/* e */ { "?"       , "?" , { NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN } },
/* f */ { "?"       , "?" , { NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN } },
};
 
 
/**
 * Convert VC920 data into usable number. Return the value, Inf on "0L" or "H",
 * NaN on error. Optional byref parameters allow to retrieve additional data.
 *
 * @param const char* s
 * @param unsigned sz
 * @param const char** unit
 * @param const char** mode
 * @param unsigned long* flags
 * @return double
 */
double vc920_analyse(const char* s, unsigned sz, const char **unit, const char **mode, unsigned long *flags)
{
  uint8_t range, funct, ac, dc, neg, man, aut;
  char digits[n_digits+1];
  int i,j;
  double value = 0;
  if(unit) *unit = NO_UNIT;
  if(mode) *mode = NO_MODE;
  if(flags)*flags = 0;
  if(strlen(s) < 11 || s[10] != '\n' || s[9] != '\r') return NAN;
  memset(digits, 0, sizeof(digits));
  range = (s[5] & 0x7);
  funct = (s[6] & 0xf);
  ac    = (s[7] & 0x1) != 0;
  dc    = (s[7] & 0x2) != 0;
  neg   = (s[8] & 0x4) != 0;
  man   = (s[8] & 0x2) != 0;
  aut   = (s[8] & 0x1) != 0;
  for(j=i=0; i<n_digits; i++) if((digits[j]=data2digit[s[i]&0xf])!=' ') j++; else digits[j] = 0;
  if(isnan(value=atof(digits))) return value;
  if(digits[strlen(digits)-1] == 'L') value =  INFINITY; // CHECK IF THIS INTERPRETATION IS CORRECT
  if(digits[strlen(digits)-1] == 'H') value = -INFINITY; //
  value *= functions[funct].ranges[range] * (neg ? -1.0 : 1.0);
  if(unit) *unit = functions[funct].unit;
  if(mode) *mode = functions[funct].name;
  if(flags) *flags = (dc<<0)|(ac<<1)|(man<<2)|(aut<<3)|(neg<<4)|(range<<5)|(funct<<8);
  return value;
}

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;
  int n=1;
 
  if(!port) seterror(E_SERIAL_NULLPOINTER, ERR_S_NULLPOINTER);
  if(!r && !w && !e) return 0;
 
  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

all: dmm
 
.phony: clean
clean:
    -@echo ""
 
dmm: vc920.c serialport.c main.c
    @gcc -Wall -O3 vc920.c serialport.c main.c -lm -o dmm