diff options
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | makefile | 11 | ||||
| -rw-r--r-- | timestamp.c | 475 | 
3 files changed, 488 insertions, 3 deletions
| @@ -11,7 +11,12 @@ CVS  *.haux  *.htoc  *.log +*.gnumeric +*.csv +*.txt  ###############  poiss +svd +timestamp @@ -1,5 +1,4 @@ - -c_mains := poiss.c svd.c +c_mains := poiss.c svd.c timestamp.c  c_sources := $(c_mains) arg_parser.c parse_csv.c  % : %.c       # cancel built-in one-step rule @@ -10,7 +9,7 @@ c_sources := $(c_mains) arg_parser.c parse_csv.c  % : %.o  	g++ $^ $(LDFLAGS) -o $@ -.PHONY : all +.PHONY : all priority  ## dependency-finding scheme (with local mods) based on:  ## http://www.gnu.org/manual/make-3.77/html_mono/make.html#SEC42 @@ -30,6 +29,12 @@ poiss : poiss.o arg_parser.o parse_csv.o  svd : svd.o arg_parser.o parse_csv.o  	g++ $^ -larmadillo -o $@ +timestamp: timestamp.o arg_parser.o +	$(CXX) $^ -lpthread -o $@ + +priority: +	sudo setcap CAP_SYS_NICE=ep ./timestamp +  ifndef NO_DOT_D    include $(c_sources:.c=.d)  endif diff --git a/timestamp.c b/timestamp.c new file mode 100644 index 0000000..6f64e78 --- /dev/null +++ b/timestamp.c @@ -0,0 +1,475 @@ +////////////////////////////////// + +#include <iostream> +using namespace std; + +void usage() { +  cout << R"EoF( +Typical usage: +:; ./timestamp -of foo.csv + +Options include: +  -h                    # print this message (and immediately exit) +  -pinout               # show wiring diagram (and immediately exit) +  -ofile $fn            # send principle output to file $fn +                        # (for stdout, use "-") +  -rts $b               # set the RTS pin to $b (0 or 1) +  -dtr $b               # set the DTR pin to $b (0 or 1) +  -flip                 # repeatedly toggle the TXD pin +  -flip -flip           # toggle TXD, suppress normal output +  -ival $nnn            # cycle-time of the flip (in microseconds) +  -verbosity            # increase verbosity +  -device $dev          # use device to take data [default /dev/ttyS0] +  -local $b             # local means ignore DSR/DCD (recommended) + +Options can be abbreviated as much as you dare. +)EoF"; +} + +void pinout() { +  cout << R"EoF( +       ** MALE (computer) (5i, 3o) ** +                        1       DCDi +        DSRi       6 +                        2       RXDi +        RTSo       7 +                        3       TXDo +        CTSi       8 +                        4       DTRo +        RNGi       9 +                        5       GND + + +       ** FEMALE (modem) (3i, 5o) ** +                        5       GND +        RNGo       9 +                        4       DTRi +        CTSo       8 +                        3       TXDi +        RTSi       7 +                        2       RXDo +        DSRo       6 +                        1       DCDo + + +            ** Observed Crossover Cable (F/F) ** +        DTE     DSE                     DSE     DTE +        logic   pin                     pin     logic +        far     near                    near    far +                                5       GND                     red +        n/c     RNGo       9 +                                4       DTRi    DSRi+DCDi +        RTSo    CTSo       8 +                                3       TXDi    RXDi            brown +orng    CTSi    RTSi       7 +                                2       RXDo    TXDo            black +        DTRo    DSRo       6 +                                1       DCDo    DTRo + +Logical RNGi cannot be set with this cable. +Logical DSRi+DCDi cannot be separated with this cable. + +References: +:; man ioctl_tty +  https://en.wikipedia.org/wiki/Null_modem +)EoF"; + +} + +#include <cmath> +#include <sstream> +#include <iomanip>      /* for setfill */ +#include <string> +#include <fstream> +#include <pthread.h> +#include <unistd.h>     /* for read(), write() */ + +#include <sys/types.h>  /* for open() */ +#include <sys/stat.h>   /* for open() */ +#include <fcntl.h>      /* for open() */ +#include <string.h>     /* for strerror */ +#include <sys/ioctl.h> +#include <linux/serial.h>     /* for serial_icounter_struct */ +#include "arg_parser.h" + +string strError(int const errnum) { +  char buf[300]; +  char* rslt = buf; +#if (_POSIX_C_SOURCE >= 200112L) && !  _GNU_SOURCE +  int sts = strerror_r(errnum, buf, sizeof(buf)); +  if (sts) throw logic_error("error in strError"); +#else +  rslt = strerror_r(errnum, buf, sizeof(buf)); +#endif +  return rslt; +} + +struct Timer : public timespec { +  Timer() : timespec{0,0} {} +  Timer& operator-=(Timer const &bbb) { +    if (tv_nsec < bbb.tv_nsec) { +      tv_nsec += 1000000000; +      tv_sec -= 1; +    } +    tv_sec -= bbb.tv_sec; +    tv_nsec -= bbb.tv_nsec; +    return *this; +  } +// works for /small/ increments: +  Timer& operator+=(double const &bbb) { +    tv_nsec += bbb*1.0e9; +    return *this; +  } + +  Timer operator-(Timer const &bbb) const { +    Timer tmp = *this; +    tmp -= bbb; +    return tmp; +  } + +  operator double() { +    return double(tv_sec) + double(tv_nsec)*1.0e-9; +  } + +  string fancy() const { +    stringstream rslt; +    tm tmx; +    memset (&tmx, 0, sizeof (tmx)); +    tm * ptr = gmtime_r(&tv_sec, &tmx); +    if (!ptr) throw runtime_error("bad gmtime"); +// RFC 2822 : Thu, 21 Oct 2021 07:36:05 +0000 +    rslt << put_time(&tmx, "%a, %d %b %Y %H:%M:%S") +        << "." << setfill('0') << setw(9) << tv_nsec +        << " +0000"; +    return rslt.str(); +  } + +  string secs() { +    stringstream rslt; +    rslt << setw(6) << tv_sec +         << "." << setfill('0') << setw(9) << tv_nsec; +    return rslt.str(); +  } +}; + +Timer etime(){ +  Timer time; +  static Timer exordium; +  clock_gettime(CLOCK_MONOTONIC, &time); +  if (exordium.tv_sec == 0) exordium = time; +  return time - exordium; +} + +string decode(int const serial) { +  string rslt; + +  if (serial & TIOCM_DTR) rslt += " DTR"; +  else rslt += " dtr"; + +// "line enable" -- not implemented on DB9 +  if (serial & TIOCM_LE) rslt += " LE"; +  else rslt += " le"; + +  if (serial & TIOCM_DSR) rslt += " DSR"; +  else rslt += " dsr"; + +  if (serial & TIOCM_CTS) rslt += " CTS"; +  else rslt += " cts"; + +  if (serial & TIOCM_RTS) rslt += " RTS"; +  else rslt += " rts"; + +  if (serial & TIOCM_CAR) rslt += " DCD"; +  else rslt += " dcd"; + +  if (serial & TIOCM_RNG) rslt += " RNG"; +  else rslt += " rng"; + +  return rslt; +} + +void setbit(int const fd, int const mask, int value) { +  int status; +  int rslt; +  rslt = ioctl(fd, TIOCMGET, &status); +  if (rslt < 0) throw invalid_argument("get status failed: " + strError(errno)); + +  if (value) status |= mask; +  else status &= ~mask; + +  rslt = ioctl(fd, TIOCMSET, &status); +  if (rslt < 0) throw invalid_argument("set status failed: " + strError(errno)); +} + +struct sitcher { +  Timer epoch; +  double delta;         // uncertainty on epoch +  int tty;              // tty file descriptor +  int fd[2];            // pipe file descriptors +  int ival;             // time to snooze in flip mode +  int verbosity; +  string ofile; +  sitcher() : ival(500000), verbosity(0), ofile("-") {} +}; + +struct messenger { +  int sts; +  Timer time; +#ifdef COUNTEM +  serial_icounter_struct cntr; +  char foobar[100]; +#endif +}; + +// :; sudo setcap CAP_SYS_NICE=ep ./timestamp +// :; sudo getcap ./timestamp +void set_realtime_priority() { +     int ret; + +     // We'll operate on the currently running thread. +     pthread_t this_thread = pthread_self(); + +// Setting thread priority is done through struct sched_param, which +// contains a sched_priority member. It’s possible to query the +// maximum and minimum priorities for a policy. + +// struct sched_param is used to store the scheduling priority +     struct sched_param params; + +// Set the priority to at most 20: +     int big = sched_get_priority_max(SCHED_FIFO); +     params.sched_priority = min(20, big); + +     cout << "About to set realtime priority = " << +                params.sched_priority << endl; + +     ret = pthread_setschedparam(this_thread, SCHED_FIFO, ¶ms); +     if (ret != 0) { +       cout << "Failed to set realtime priority: " +              << strError(errno) +              << endl; +       return; +     } + +// Verify policy: +     int policy = 0; + +// Verify the change in thread priority +     ret = pthread_getschedparam(this_thread, &policy, ¶ms); +     if (ret != 0) { +       cout << "Couldn't retrieve real-time scheduling paramers" << endl; +     } else { +       if(policy != SCHED_FIFO) { +           cout << "Scheduling is NOT SCHED_FIFO!" << endl; +       } else { +           cout << "Policy is SCHED_FIFO as expected." << endl; +       } +       cout << "Thread priority is " << params.sched_priority << endl; +     } +} + + +// write status into fd[1] +void *grabber(void* _sitch) { +  sitcher& sitch(*(sitcher*)(_sitch)); +  size_t rslt; +  messenger msg; +  size_t ss(sizeof(msg)); + +  set_realtime_priority(); + +  while(1){ +    msg.time = etime(); + +    msg.sts = -1;          // in case ioctl bombs out +    rslt = ioctl(sitch.tty, TIOCMGET, &msg.sts); +    if (rslt < 0) throw invalid_argument("get status failed"); +    if (msg.sts < 0) throw logic_error("get status bombed out"); +#ifdef COUNTEM +    rslt = ioctl(sitch.tty, TIOCGICOUNT, &msg.cntr); +    if (rslt < 0) throw invalid_argument("count failed"); +#endif + +    rslt = write (sitch.fd[1], &msg, ss); +    if (rslt != ss) throw invalid_argument("write failed"); + +    int mask = TIOCM_RNG | TIOCM_DSR | TIOCM_CD | TIOCM_CTS; +    rslt = ioctl(sitch.tty, TIOCMIWAIT, &mask); +    if (rslt < 0) throw invalid_argument("wait failed"); +  } +} + +// read and interpret status +void *saver(void* _sitch) { +  sitcher& sitch(*(sitcher*)(_sitch)); +  Timer prev; +  if (sitch.ofile == "") return 0; +  ofstream xxout; +  if (sitch.ofile != "-") { +    xxout.open(sitch.ofile); +  } +  ostream& xout(sitch.ofile != "-" ? xxout : cout); + +  xout << "Epoch:, " << sitch.epoch.fancy() << endl; +  xout << "Epoch:, " << sitch.epoch.secs() +        << ",±," << fixed << setprecision(6) << sitch.delta << endl; +  xout << endl; +  xout << endl; + +  while(1){ +    messenger msg; +    size_t ss(sizeof(msg)); +    size_t rslt; + +    rslt = read (sitch.fd[0], &msg, ss); +    if (rslt != ss) throw invalid_argument("read failed"); +    xout << msg.time.secs() +         << ",  0x"  << hex << msg.sts; + +    if (sitch.verbosity) { +      xout << ", " << fixed << setprecision(5) << double(msg.time - prev) +         << ",  " << decode(msg.sts); +    } + +    xout << endl; +    prev = msg.time; +  } +} + +void snooze(int const interval) { +  timespec now = etime(); +  double frac = now.tv_nsec / 1000.; +  double drift = fmod(frac, interval); +  usleep(interval - drift); +} + +void *flippy(void* _sitch) { +  sitcher& sitch(*(sitcher*)(_sitch)); +  int rslt; +  while (1) { +    usleep(sitch.ival/20.); +    rslt = ioctl(sitch.tty, TIOCCBRK, 0); +    if (rslt < 0) throw invalid_argument("cancel break failed"); +    snooze(sitch.ival); +    rslt = ioctl(sitch.tty, TIOCSBRK, 0); +    if (rslt < 0) throw invalid_argument("send break failed"); +  } +} + +int main(int argc, char * const * argv) { +  string device("/dev/ttyS0"); +  int flipmode(0); +  int dtr(0); +  int dsr(0); +  int cts(0); +  int rts(0); +  int le(0);            // "line enable" -- not used +// "local" means ignore modem status DSR and/or DCD +  int local(1);         // bad things happen if not local + +  sitcher sitch; +  Timer start2; +  clock_gettime(CLOCK_REALTIME, &sitch.epoch); +  etime(); +  clock_gettime(CLOCK_REALTIME, &start2); +  sitch.delta = start2 - sitch.epoch; +  sitch.delta /= 2.0; +  sitch.epoch += sitch.delta; +  arg_parser arg(argc, argv); +  arg.fold_case = 1; +  string progname = arg.nextRaw(); + +  for (;;) { +    string raw_arg = arg.nextRaw();         // get next keyword +    if (arg.fail()) break; +    if (0) {} +    else if (arg.prefix("-help")) { +      usage(); +      exit(0); +    } +    else if (arg.prefix("-pinout")) { +      pinout(); +      exit(0); +    } +    else if (arg.prefix("-verbosity")) { +      sitch.verbosity++; +    } +    else if (arg.prefix("-flip")) { +      flipmode++; +    } +    else if (arg.prefix("-device")) { +      arg >> device; +    } +    else if (arg.prefix("-ival")) { +      arg >> sitch.ival; +    } +    else if (arg.prefix("-dtr")) { +      arg >> dtr; +    } +    else if (arg.prefix("-le")) { +      arg >> le; +    } +    else if (arg.prefix("-local")) { +      arg >> local; +    } +    else if (arg.prefix("-dsr")) { +      arg >> dsr; +    } +    else if (arg.prefix("-cts")) { +      arg >> cts; +    } +    else if (arg.prefix("-rts")) { +      arg >> rts; +    } +    else if (arg.prefix("-ofile")) { +      arg >> sitch.ofile; +    } +    else { +      cerr << "Unrecognized: '" << raw_arg << "'" << endl; +      exit(1); +    } +  } + +  cout << "Epoch:, " << sitch.epoch.fancy() << endl; +  cout << "Epoch:, " << sitch.epoch.secs() +        << ",±," << setprecision(6) << sitch.delta << endl; + +  int rslt; +  sitch.tty = open(device.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY); +  if (sitch.tty < 0) throw invalid_argument("open failed: " + device); + +  int tmp(-99); +  rslt = ioctl(sitch.tty, TIOCGSOFTCAR, &tmp); +  if (rslt < 0) throw invalid_argument("get local failed"); +  cout << "tty 'local' flag was previously: " << tmp << endl; +  rslt = ioctl(sitch.tty, TIOCSSOFTCAR, &local); +  if (rslt < 0) throw invalid_argument("set local failed"); +  rslt = ioctl(sitch.tty, TIOCSBRK, 0); +  if (rslt < 0) throw invalid_argument("send break failed"); + +  if (dsr || cts || le) +        cerr << "Beware setting DSR|CTS|LE has no effect AFAICT" << endl; +  setbit(sitch.tty, TIOCM_DTR, dtr); +  setbit(sitch.tty, TIOCM_DSR, dsr); +  setbit(sitch.tty, TIOCM_LE , le); +  setbit(sitch.tty, TIOCM_CTS, cts); +  setbit(sitch.tty, TIOCM_RTS, rts); + +  pthread_t threads(0), threadg(0), threadf(0); + +  rslt = pipe (sitch.fd); +  if (rslt < 0) throw invalid_argument("pipe failed"); + +  if (flipmode) { +    pthread_create(&threadf, NULL, flippy, (void*)(&sitch)); +  } + +  if (flipmode < 2) { +    pthread_create(&threadg, NULL, grabber, (void*)(&sitch)); +    pthread_create(&threads, NULL, saver, (void*)(&sitch)); +  } + +  if(threads) pthread_join(threads, NULL); +  if(threadg) pthread_join(threadg, NULL); +  if(threadf) pthread_join(threadf, NULL); +} | 
