From a9e91095d042b8e25aec714ccd3eb5233679eee9 Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 21 Oct 2021 19:26:57 -0700 Subject: add rudimentary program to timestamp serial port events --- .gitignore | 5 + makefile | 11 +- timestamp.c | 475 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 timestamp.c diff --git a/.gitignore b/.gitignore index 60f56d2..da1437a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,12 @@ CVS *.haux *.htoc *.log +*.gnumeric +*.csv +*.txt ############### poiss +svd +timestamp diff --git a/makefile b/makefile index 13b8e92..6cb1aeb 100644 --- a/makefile +++ b/makefile @@ -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 +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 +#include +#include /* for setfill */ +#include +#include +#include +#include /* for read(), write() */ + +#include /* for open() */ +#include /* for open() */ +#include /* for open() */ +#include /* for strerror */ +#include +#include /* 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); +} -- cgit v1.2.3