aboutsummaryrefslogtreecommitdiff
path: root/timestamp.c
diff options
context:
space:
mode:
Diffstat (limited to 'timestamp.c')
-rw-r--r--timestamp.c475
1 files changed, 475 insertions, 0 deletions
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, &params);
+ 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, &params);
+ 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);
+}