···11+Copyright (c) 2021 joshua stein <jcs@jcs.org>
22+33+Permission to use, copy, modify, and distribute this software for any
44+purpose with or without fee is hereby granted, provided that the above
55+copyright notice and this permission notice appear in all copies.
66+77+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
···11+## chronosync
22+33+A small utility to set the clock on a
44+[Hayes Stack Chronograph](http://atari8bitads.blogspot.com/2017/06/theres-no-better-time.html)
55+over its serial port.
66+77+### Synopsis
88+99+ chronosync [-d] [-s serial speed] <serial device>
1010+1111+### Compiling
1212+1313+Compile with a BSD Make:
1414+1515+ $ make
1616+1717+Install:
1818+1919+ # make install
2020+2121+Run and pass argument of the serial device connected to the Chronograph:
2222+2323+ # chronosync /dev/cua02
2424+2525+The computer's local time is sent to the Chronograph.
2626+2727+### Notes
2828+2929+Since the Chronograph only supports setting the time to minute precision with
3030+`ATST`, `chronosync` will sleep until zero seconds of the next minute before
3131+sending the time.
3232+3333+The clock on the Chronograph will probably only need to be set once per day to
3434+stay accurate, so running this from cron once a day should suffice:
3535+3636+ # sync time at 2am
3737+ 1 59 * * * /usr/local/bin/chronosync /dev/cua02
+168
chronosync.c
···11+/*
22+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ */
1616+1717+#include <err.h>
1818+#include <errno.h>
1919+#include <fcntl.h>
2020+#include <poll.h>
2121+#include <stdio.h>
2222+#include <stdlib.h>
2323+#include <string.h>
2424+#include <termios.h>
2525+#include <time.h>
2626+#include <unistd.h>
2727+#include <vis.h>
2828+#include <sys/ioctl.h>
2929+#include <sys/time.h>
3030+#include <sys/types.h>
3131+3232+static int debug = 0;
3333+static char vbuf[512];
3434+static int serial_fd = -1;
3535+3636+void usage(const char *);
3737+void serial_setup(int, speed_t);
3838+size_t serial_read(void *, size_t);
3939+size_t serial_write(const void *, size_t);
4040+4141+void
4242+usage(const char *progname)
4343+{
4444+ fprintf(stderr, "usage: %s [-d] [-s serial speed] <serial device>\n",
4545+ progname);
4646+ exit(1);
4747+}
4848+4949+void
5050+serial_setup(int fd, speed_t speed)
5151+{
5252+ struct termios tty;
5353+5454+ if (ioctl(fd, TIOCEXCL) != 0)
5555+ err(1, "ioctl(TIOCEXCL)");
5656+ if (tcgetattr(fd, &tty) < 0)
5757+ err(1, "tcgetattr");
5858+5959+ tty.c_cflag |= CREAD;
6060+ tty.c_cflag &= ~CSIZE;
6161+ tty.c_cflag |= CS8; /* 8-bit characters */
6262+ tty.c_cflag &= ~PARENB; /* no parity bit */
6363+ tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
6464+6565+ /* setup for non-canonical mode */
6666+ tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
6767+ tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
6868+ tty.c_oflag &= ~OPOST;
6969+7070+ /* fetch bytes as they become available */
7171+ tty.c_cc[VMIN] = 1;
7272+ tty.c_cc[VTIME] = 1;
7373+7474+ cfsetspeed(&tty, speed);
7575+7676+ if (tcsetattr(fd, TCSAFLUSH, &tty) != 0)
7777+ err(1, "tcsetattr");
7878+}
7979+8080+size_t
8181+serial_read(void *buf, size_t nbytes)
8282+{
8383+ size_t ret;
8484+8585+ ret = read(serial_fd, buf, nbytes);
8686+8787+ if (debug && ret > 0) {
8888+ strvisx(vbuf, buf, ret, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
8989+ printf("<<< %s\n", vbuf);
9090+ }
9191+9292+ return ret;
9393+}
9494+9595+size_t
9696+serial_write(const void *buf, size_t nbytes)
9797+{
9898+ if (debug) {
9999+ strvisx(vbuf, buf, nbytes, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
100100+ printf(">>> %s\n", vbuf);
101101+ }
102102+103103+ return write(serial_fd, buf, nbytes);
104104+}
105105+106106+int
107107+main(int argc, char *argv[])
108108+{
109109+ struct timeval tp;
110110+ struct tm *tm;
111111+ char *serial_dev = NULL;
112112+ char buf[64];
113113+ int serial_speed = B1200;
114114+ int ch;
115115+116116+ if (argc == 1)
117117+ usage(argv[0]);
118118+119119+ while ((ch = getopt(argc, argv, "ds:")) != -1) {
120120+ switch (ch) {
121121+ case 'd':
122122+ debug = 1;
123123+ break;
124124+ case 's':
125125+ serial_speed = (unsigned)strtol(optarg, NULL, 0);
126126+ if (errno)
127127+ err(1, "invalid serial port speed value");
128128+ break;
129129+ default:
130130+ usage(argv[0]);
131131+ }
132132+ }
133133+ argc -= optind;
134134+ argv += optind;
135135+136136+ if (argc == 0)
137137+ usage(argv[0]);
138138+139139+ serial_dev = argv[0];
140140+ serial_fd = open(serial_dev, O_RDWR|O_SYNC);
141141+ if (serial_fd < 0)
142142+ err(1, "open %s", serial_dev);
143143+144144+ serial_setup(serial_fd, serial_speed);
145145+146146+ for (;;) {
147147+ gettimeofday(&tp, NULL);
148148+ tm = localtime(&tp.tv_sec);
149149+ if (debug)
150150+ printf("currently: %02d:%02d:%02d\n", tm->tm_hour,
151151+ tm->tm_min, tm->tm_sec);
152152+ if (tm->tm_sec == 0) {
153153+ if (debug)
154154+ printf("setting chronograph to %02d%02d00\n",
155155+ tm->tm_hour, tm->tm_min);
156156+ sprintf(buf, "\rATST%02d%02d00\r", tm->tm_hour,
157157+ tm->tm_min);
158158+ serial_write(buf, strlen(buf));
159159+ break;
160160+ } else {
161161+ usleep(500000); /* half a sec */
162162+ }
163163+ }
164164+165165+ close(serial_fd);
166166+167167+ return 0;
168168+}