////////////////////////////////// #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); }