From d008ca6e5dc96cd6210a2494b1a1499a063baf2d Mon Sep 17 00:00:00 2001 From: c vw Date: Wed, 16 Aug 2017 09:09:10 +0200 Subject: [PATCH] Microham support. Use "uh-rig" for rig_pathname to talk to the microHam device for CAT. If you want hardware PTT via the microham device, use DTR method and ptt_pathname "uh-ptt". --- NEWS | 1 + doc/utility_programs.texi | 20 +- src/Makefile.am | 2 +- src/iofunc.c | 36 ++ src/microham.c | 942 ++++++++++++++++++++++++++++++++++++++ src/microham.h | 17 + src/serial.c | 157 ++++++- tests/rigctl.1 | 19 +- tests/rigctld.1 | 3 +- tests/rotctl.1 | 9 +- tests/rotctld.1 | 3 +- 11 files changed, 1184 insertions(+), 25 deletions(-) create mode 100644 src/microham.c create mode 100644 src/microham.h diff --git a/NEWS b/NEWS index 90802ab01..ae8ab2ddc 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ Version 3.2 * New model, FT-891. Mike, W9MDB * Build instructions and test script for Python3 * Rename autogen.sh to bootsrap and don't call configure + * micro-ham support. Christoph, DL1YCF Version 3.1 2016-12-31 diff --git a/doc/utility_programs.texi b/doc/utility_programs.texi index bf93de5bd..28d6320dd 100644 --- a/doc/utility_programs.texi +++ b/doc/utility_programs.texi @@ -249,7 +249,9 @@ model 2 for NET rigctl (@command{rigctld}). Use @var{device} as the file name of the port the radio is connected. Often a serial port, but could be a USB to serial adapter. Typically @file{/dev/ttyS0} , @file{/dev/ttyS1} , @file{/dev/ttyUSB0} , etc.@: -on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. +on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. The +special string @kbd{uh-rig} may be given to enable micro-ham device +support. @ifhtml @* @end ifhtml @@ -1308,11 +1310,13 @@ Select rotator model number. See model list (use @kbd{rotctl -l}). rotor model 2 for NET rotctl (@command{rotctld}). @item -r -@itemx --rig-file=@var{device} +@itemx --rot-file=@var{device} Use @var{device} as the file name of the port the rotor is connected. Often a serial port, but could be a USB to serial adapter. Typically @file{/dev/ttyS0} , @file{/dev/ttyS1} , @file{/dev/ttyUSB0} , etc.@: -on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. +on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. The +special string @kbd{uh-rig} may be given to enable micro-ham device +support. @ifhtml @* @end ifhtml @@ -1776,7 +1780,9 @@ Select radio model number. See model list (use @kbd{rigctld -l}). Use @var{device} as the file name of the port the radio is connected. Often a serial port, but could be a USB to serial adapter. Typically @file{/dev/ttyS0} , @file{/dev/ttyS1} , @file{/dev/ttyUSB0} , etc.@: -on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. +on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. The +special string @kbd{uh-rig} may be given to enable micro-ham device +support. @ifhtml @* @end ifhtml @@ -2861,11 +2867,13 @@ Select rotator model number. See model list (use @kbd{rotctl -l}). rotor model 2 for NET rotctl (@command{rotctld}). @item -r -@itemx --rig-file=@var{device} +@itemx --rot-file=@var{device} Use @var{device} as the file name of the port the rotor is connected. Often a serial port, but could be a USB to serial adapter. Typically @file{/dev/ttyS0} , @file{/dev/ttyS1} , @file{/dev/ttyUSB0} , etc.@: -on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. +on Linux or @file{COM1} , @file{COM2} , etc.@: on MS Windows. The +special string @kbd{uh-rig} may be given to enable micro-ham device +support. @ifhtml @* @end ifhtml diff --git a/src/Makefile.am b/src/Makefile.am index c1668542e..d8ace1c1b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ RIGSRC = rig.c serial.c serial.h misc.c misc.h register.c register.h event.c \ event.h cal.c cal.h conf.c tones.c tones.h rotator.c locator.c rot_reg.c \ rot_conf.c rot_conf.h iofunc.c iofunc.h ext.c mem.c settings.c \ parallel.c parallel.h usb_port.c usb_port.h debug.c network.c network.h \ - cm108.c cm108.h gpio.c gpio.h idx_builtin.h token.h par_nt.h + cm108.c cm108.h gpio.c gpio.h idx_builtin.h token.h par_nt.h microham.c microham.h lib_LTLIBRARIES = libhamlib.la libhamlib_la_SOURCES = $(RIGSRC) diff --git a/src/iofunc.c b/src/iofunc.c index 7a5a689af..2c117b21f 100644 --- a/src/iofunc.c +++ b/src/iofunc.c @@ -223,6 +223,12 @@ int HAMLIB_API port_close(hamlib_port_t *p, rig_port_t port_type) #if defined(WIN32) && !defined(HAVE_TERMIOS_H) #include "win32termios.h" +/* + * We need uh_radio_fd to determine wether to use win32_serial_read() etc. + * or (simply) read(). + */ +#include "microham.h" + /* On MinGW32/MSVC/.. the appropriate accessor must be used * depending on the port type, sigh. @@ -232,6 +238,16 @@ static ssize_t port_read(hamlib_port_t *p, void *buf, size_t count) int i; ssize_t ret; + /* + * Since WIN32 does its special serial read, we have + * to catch the microHam case to do just "read". + * Note that we always have RIG_PORT_SERIAL in the + * microHam case. + */ + if (p->fd == uh_radio_fd) { + return read(p->fd, buf, count); + } + if (p->type.rig == RIG_PORT_SERIAL) { ret = win32_serial_read(p->fd, buf, count); @@ -255,6 +271,16 @@ static ssize_t port_read(hamlib_port_t *p, void *buf, size_t count) static ssize_t port_write(hamlib_port_t *p, const void *buf, size_t count) { + /* + * Since WIN32 does its special serial write, we have + * to catch the microHam case to do just "write". + * Note that we always have RIG_PORT_SERIAL in the + * microHam case. + */ + if (p->fd == uh_radio_fd) { + return write(p->fd, buf, count); + } + if (p->type.rig == RIG_PORT_SERIAL) { return win32_serial_write(p->fd, buf, count); } else if (p->type.rig == RIG_PORT_NETWORK @@ -287,6 +313,16 @@ static int port_select(hamlib_port_t *p, int n, fd_set *readfds, exceptfds = NULL; #endif + /* + * Since WIN32 does its special serial select, we have + * to catch the microHam case to do just "select". + * Note that we always have RIG_PORT_SERIAL in the + * microHam case. + */ + if (p->fd == uh_radio_fd) { + return select(n, readfds, writefds, exceptfds, timeout); + } + if (p->type.rig == RIG_PORT_SERIAL) { return win32_serial_select(n, readfds, writefds, exceptfds, timeout); } else { diff --git a/src/microham.c b/src/microham.c new file mode 100644 index 000000000..56e2ec49c --- /dev/null +++ b/src/microham.c @@ -0,0 +1,942 @@ +// +// This file contains the support for microHam devices +// if this system does not have pthreads, there is no microHam support, but it compiles +// on WIN32, this file compiles but no microHam device will be found and openend +// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//#define FRAME(s, ...) printf(s, ##__VA_ARGS__) +//#define DEBUG(s, ...) printf(s, ##__VA_ARGS__) +//#define TRACE(s, ...) printf(s, ##__VA_ARGS__) +//#define ERROR(s, ...) printf(s, ##__VA_ARGS__) + +#ifndef FRAME +#define FRAME(s, ...) +#endif +#ifndef DEBUG +#define DEBUG(s, ...) +#endif +#ifndef TRACE +#define TRACE(s, ...) +#endif +#ifndef ERROR +#define ERROR(s, ...) +#endif + +#ifndef PATH_MAX +// should not happen, should be defined in limits.h +// but better paranoia than a code that does not work +#define PATH_MAX 256 +#endif + +static char uh_device_path[PATH_MAX]; // use PATH_MAX since udev names can be VERY long! +static int uh_device_fd=-1; +static int uh_is_initialized=0; + +static int uh_radio_pair[2] = {-1, -1}; +static int uh_ptt_pair[2] = {-1, -1}; +static int uh_wkey_pair[2] = {-1, -1}; + +static int uh_radio_in_use; +static int uh_ptt_in_use; +static int uh_wkey_in_use; + +static int statusbyte=0; + +#ifdef HAVE_PTHREAD + +#include + +static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; +static pthread_t readthread; +#define getlock() if (pthread_mutex_lock(&mutex)) perror("GETLOCK:") +#define freelock() if (pthread_mutex_unlock(&mutex)) perror("FREELOCK:") +#else +#define getlock() +#define freelock() +#endif + +// +// time of last heartbeat. Updated by heartbeat() +// +static time_t lastbeat=0; +static time_t starttime; + +#define TIME ((int) (time(NULL) - starttime)) + +// +// close all sockets and mark them free +// +static void close_all_files() +{ + if (uh_radio_pair[0] >= 0) { + close(uh_radio_pair[0]); + } + if (uh_radio_pair[1] >= 0) { + close(uh_radio_pair[1]); + } + if (uh_ptt_pair[0] >= 0) { + close(uh_ptt_pair[0]); + } + if (uh_ptt_pair[1] >= 0) { + close(uh_ptt_pair[1]); + } + if (uh_wkey_pair[0] >= 0) { + close(uh_wkey_pair[0]); + } + if (uh_wkey_pair[1] >= 0) { + close(uh_wkey_pair[1]); + } + uh_radio_pair[0]=-1; + uh_radio_pair[1]=-1; + uh_ptt_pair[0]=-1; + uh_ptt_pair[1]=-1; + uh_wkey_pair[0]=-1; + uh_wkey_pair[1]=-1; + uh_radio_in_use=0; + uh_ptt_in_use=0; + uh_wkey_in_use=0; +// finally, close connection to microHam device + if (uh_device_fd >= 0) { + close(uh_device_fd); + } +} + +static void close_microham() +{ + if (!uh_is_initialized) { + return; + } + TRACE("%10d:Closing MicroHam device\n",TIME); + uh_is_initialized=0; +#ifdef HAVE_PTHREAD + // wait for read_device thread to finish + pthread_join(readthread, NULL); +#endif + close_all_files(); +} + +#if defined(WIN32) +/* + * On Windows, this is not really needed since we have uhrouter.exe + * creating virtual COM ports + * + * I do now know how to "find" a microham device under Windows, + * and serial I/O may also be Windows-specific. + * + * Therefore, a dummy version of finddevices() is included such that it compiles + * well on WIN32. Since the dummy version does not find anything, no reading thread + * and no sockets are created. + * + * What finddevices() must do: + * Scan all USB-serial ports with an FTDI chip, and look + * for its serial number, take the first port you can find where the serial + * number begins with MK, M2, CK, DK, D2, 2R, 2P or UR. Then, open the serial + * line and put a valid fd into uh_device_fd. + */ +static void finddevices() +{ +} +#else + +/* + * POSIX (including APPLE) + * + * Finding microHam devices can be a mess. + * On Apple, the FTDI chips inside the microHam device show up as + * /dev/tty.usbserial- where is the serial number + * of the chip. So we can easily look for MK, MK-II, DK etc. + * devices. + * + * On LINUX, these show up as /dev/ttyUSBx where x=0,1,2... + * depending on when the device has been recognized. Fortunately + * today all LINUX boxes have udev, and there is a symlink + * in /dev/serial/by-id containing the serial number pointing + * to the relevant special file in /dev. + * + * This technique is used such that we do not need to open + * ALL serial ports in the system looking which one corresponds + * to microHam. Note we could use libusb directly, but this + * probably requires root privileges. + * + * Below is support for MacOS and LINUX, but I do not know + * how to "find" a microHam device under Windows. Perhaps + * someone knows, but for Windows there is uhrouter.exe that + * creates virtual COM-ports such that microHam support is + * not really needed. + * + * Note: StationMaster used a different protocol, and + * the protocol of StationMaster DeLuxe is not + * even disclosed. + */ + +#define NUMUHTYPES 8 +static struct uhtypes { + const char *name; + const char *device; +} uhtypes[NUMUHTYPES] = { + +#ifdef __APPLE__ + { "microKeyer", "/dev/tty.usbserial-MK*"}, + { "microKeyer-II", "/dev/tty.usbserial-M2*"}, + { "CW Keyer", "/dev/tty.usbserial-CK*"}, + { "digiKeyer", "/dev/tty.usbserial-DK*"}, + { "digiKeyer-II", "/dev/tty.usbserial-D2*"}, + { "micorKeyer-IIR", "/dev/tty.usbserial-2R*"}, + { "microKeyer-IIR+", "/dev/tty.usbserial-2P*"}, + { "microKeyer-U2R", "/dev/tty.usbserial-UR*"}, +#else + { "microKeyer", "/dev/serial/by-id/*microHAM*_MK*"}, + { "microKeyer-II", "/dev/serial/by-id/*microHAM*_M2*"}, + { "CW Keyer", "/dev/serial/by-id/*microHAM*_CK*"}, + { "digiKeyer", "/dev/serial/by-id/*microHAM*_DK*"}, + { "digiKeyer-II", "/dev/serial/by-id/*microHAM*_D2*"}, + { "micorKeyer-IIR", "/dev/serial/by-id/*microHAM*_2R*"}, + { "microKeyer-IIR+", "/dev/serial/by-id/*microHAM*_2P*"}, + { "microKeyer-U2R", "/dev/serial/by-id/*microHAM*_UR*"}, +#endif +}; + +// +// Find a microHamDevice. Here we assume that the device special +// file has a name from which we can tell this is a microHam device +// This is the case for MacOS and LINUX (for LINUX: use udev) +// +#include +static void finddevices() +{ + struct stat st; + glob_t gbuf; + int i,j; + struct termios TTY; + int fd; + + uh_device_fd= -1; // indicates "no device is found" + + // + // Check ALL device special files that might be relevant, + // + for (i=0; i= PATH_MAX) { + // I do not know if this can happen, but if it happens, we just skip the device. + ERROR("Name too long: %s\n",gbuf.gl_pathv[j]); + continue; + } + DEBUG("%s is a character special file\n", gbuf.gl_pathv[j]); + strcpy(uh_device_path, gbuf.gl_pathv[j]); + TRACE("Found a %s, Device=%s\n",uhtypes[i].name,uh_device_path); + + fd = open(uh_device_path, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) { + ERROR("Cannot open serial port %s\n",uh_device_path); + perror("Open:"); + continue; + } + tcflush(fd, TCIFLUSH); + if (tcgetattr( fd, &TTY)) { + ERROR("Cannot get comm params\n"); + close(fd); + continue; + } + // microHam devices always use 230400 baud, 8N1, only TxD/RxD is used (no h/w handshake) + // 8 data bits + TTY.c_cflag &= ~CSIZE; + TTY.c_cflag |= CS8; + // enable receiver, set local mode + TTY.c_cflag |= (CLOCAL | CREAD); + // no parity + TTY.c_cflag &= ~PARENB; + // 1 stop bit + TTY.c_cflag &= ~CSTOPB; + + cfsetispeed(&TTY, B230400); + cfsetospeed(&TTY, B230400); + + // NO h/w handshake + // TTY.c_cflag &= ~CRTSCTS; + // raw input + TTY.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + // raw output + TTY.c_oflag &= ~OPOST; + // software flow control disabled + // TTY.c_iflag &= ~IXON; + // do not translate CR to NL + // TTY.c_iflag &= ~ICRNL; + + // timeouts + TTY.c_cc[VMIN]=0; + TTY.c_cc[VTIME]=255; + + if (tcsetattr(fd, TCSANOW, &TTY)) { + ERROR("Can't set device communication parameters"); + close (fd); + continue; + } + + TRACE("SerialPort opened: %s fd=%d\n",uh_device_path,fd); + uh_device_fd=fd; + // The first time we were successfull, we skip all what might come + return; + } + } + } + } +} +#endif + +// +// parse a frame received from the keyer +// This is called from the "device reading" thread +// once a complete frame has been received +// Send Radio and Winkey bytes received to the client sockets. +// +static int frameseq=0; +static int incontrol=0; +static unsigned char controlstring[256]; +static int numcontrolbytes=0; + +static void parseFrame(unsigned char *frame) +{ + int i; + unsigned char byte; + FRAME("RCV frame %02x %02x %02x %02x\n",frame[0],frame[1],frame[2],frame[3]); + + // frames come in sequences. The first frame of a sequence has bit6 cleared in the headerbyte. + if ((frame[0] & 0x40) == 0) { + frameseq=0; + } else { + frameseq++; + } + + // A frame is of the form header-byte1-byte2-byte3 + // byte 1 is always RADIO, byte 2 is always RADIO2/AUX (if validity bit set) + // byte 3 has different meaning, depending on the sequence number of the frame: + // seq=0 -> Flags + // seq=1 -> control + // seq=2 -> WinKey + // seq=3 -> PS2 + + // if RADIO byte is valid, send it to client + if ((frame[0] & 0x20) != 0) { + byte=frame[1] & 0x7F; + if ((frame[0] & 0x04) != 0) { + byte |= 0x80; + } + DEBUG("%10d:FromRadio: %02x\n", TIME, byte); + if (write(uh_radio_pair[0], &byte, 1) != 1) { + ERROR("Write Radio Socket\n"); + } + } + + // ignore AUX/RADIO2 for the time being + + // check the shared channel for validity, if it is the CONTROL channel it is always valid + if ((frame[0] & 0x08) || (frameseq == 1)) { + byte=frame[3] & 0x7F; + if (frame[0] & 0x01) { + byte |= 0x80; + } + + switch (frameseq) { + case 0: + DEBUG("%10d:RCV: Flags=%02x\n", TIME, byte); + // No reason to pass the flags to clients + break; + case 1: + if ((frame[0] & 0x08) == 0 && !incontrol) { + // start or end of a control sequence + numcontrolbytes=1; + controlstring[0]=byte; + incontrol=1; + break; + } + if ((frame[0] & 0x08) == 0 && incontrol) { + // end of a control sequence + controlstring[numcontrolbytes++]=byte; + DEBUG("%10d:FromControl:",TIME); + for (i=0; i 5) { + heartbeat(); + } + // + // Wait for something to arrive, either from the microham device + // or from the sockets used for I/O from hamlib. + // If nothing arrives within 100 msec, restart the "infinite loop". + // + FD_ZERO(&fds); + FD_SET(uh_device_fd, &fds); + FD_SET(uh_radio_pair[0], &fds); + FD_SET(uh_ptt_pair[0], &fds); + FD_SET(uh_wkey_pair[0], &fds); + + // determine max of these fd's for use in select() + maxdev=uh_device_fd; + if (uh_radio_pair[0] > maxdev) { + maxdev=uh_radio_pair[0]; + } + if (uh_ptt_pair[0] > maxdev) { + maxdev=uh_ptt_pair[0]; + } + if (uh_wkey_pair[0] > maxdev) { + maxdev=uh_wkey_pair[0]; + } + + tv.tv_usec=100000; + tv.tv_sec=0; + ret=select(maxdev+1, &fds, NULL, NULL, &tv); + // + // select returned error, or nothing has arrived: + // continue "infinite" loop. + // + if (ret <=0) { + continue; + } + // + // Take care of the incoming data (microham device, sockets) + // + if (FD_ISSET(uh_device_fd, &fds)) { + // compose frame, if it is complete, all parseFrame + while(read(uh_device_fd, buf, 1) > 0) { + if (!(buf[0] & 0x80) && framepos != 0) { + ERROR("FrameSyncStartError\n"); + framepos=0; + } + if ((buf[0] & 0x80) && framepos == 0) { + ERROR("FrameSyncStartError\n"); + framepos=0; + continue; + } + frame[framepos++]=buf[0]; + if (framepos >= 4) { + framepos=0; + parseFrame(frame); + } + } + } + if (FD_ISSET(uh_ptt_pair[0], &fds)) { + // we do not expect any data here, but drain the socket + while(read(uh_ptt_pair[0], buf, 1) >0) { + // do nothing + } + } + if (FD_ISSET(uh_radio_pair[0], &fds)) { + // read everything that is there, and send it to the radio + while(read(uh_radio_pair[0], buf, 1) >0) { + writeRadio(buf, 1); + } + } + if (FD_ISSET(uh_wkey_pair[0], &fds)) { + // read everything that is there, and send it to the WinKey chip + while(read(uh_wkey_pair[0], buf, 1) >0) { + writeWkey(buf, 1); + } + } + } +} + +/* + * If we do not have pthreads, we cannot use the microham device. + * This is so because we have to digest unsolicited messages + * (e.g. voltage change) and since we have to send periodic + * heartbeats. + * Nevertheless, the program should compile well even we we do not + * have pthreads, in this case start_thread is a dummy since uh_is_initialized + * is never set. + */ +static void start_thread() +{ +#ifdef HAVE_PTHREAD + /* + * Find a microHam device and open serial port to it. + * If successful, create sockets for doing I/O from within hamlib + * and start a thread to listen to the "other ends" of the sockets + */ + int ret, fail; + unsigned char buf[4]; + pthread_attr_t attr; + + if (uh_is_initialized) { + return; // PARANOIA: this should not happen + } + finddevices(); + if (uh_device_fd < 0) { + ERROR("Could not open any microHam device.\n"); + return; + } + // Create socket pairs + if (socketpair(AF_UNIX, SOCK_STREAM, 0, uh_radio_pair) < 0) { + perror("RadioPair:"); + return; + } + if (socketpair(AF_UNIX, SOCK_STREAM, 0, uh_ptt_pair) < 0) { + perror("PTTPair:"); + return; + } + if (socketpair(AF_UNIX, SOCK_STREAM, 0, uh_wkey_pair) < 0) { + perror("WkeyPair:"); + return; + } + + DEBUG("RADIO sockets: server=%d client=%d\n", uh_radio_pair[0], uh_radio_pair[1]); + DEBUG("PTT sockets: server=%d client=%d\n", uh_ptt_pair[0], uh_ptt_pair[1]); + DEBUG("WinKey sockets: server=%d client=%d\n", uh_wkey_pair[0], uh_wkey_pair[1]); + + // + // Make the sockets nonblocking + // First try if we can set flags, then do set the flags + // + + fail=0; + + ret=fcntl(uh_radio_pair[0], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_radio_pair[0], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + ret=fcntl(uh_ptt_pair[0], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_ptt_pair[0], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + ret=fcntl(uh_wkey_pair[0], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_wkey_pair[0], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + ret=fcntl(uh_radio_pair[1], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_radio_pair[1], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + ret=fcntl(uh_ptt_pair[1], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_ptt_pair[1], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + ret=fcntl(uh_wkey_pair[1], F_GETFL, 0); + if (ret != -1) { + ret=fcntl(uh_wkey_pair[1], F_SETFL, ret | O_NONBLOCK); + } + if (ret == -1) { + fail=1; + } + + // + // If something went wrong, close everything and return + // + if (fail) { + close_all_files(); + return; + } + + // drain input from microHam device + while(read(uh_device_fd, buf, 1) >0) { + // do_nothing + } + + uh_is_initialized=1; + starttime=time(NULL); + + // Do some heartbeats to sync-in + heartbeat(); + heartbeat(); + heartbeat(); + + // Set keyer mode to DIGITAL + buf[0]=0x0A; buf[1]=0x03; buf[2]=0x8a; writeControl(buf,3); + + // Start background thread reading the microham device and the sockets + pthread_attr_init(&attr); + ret=pthread_create(&readthread, &attr, read_device, NULL); + if (ret != 0) { + ERROR("Could not start read_device thread\n"); + close_all_files(); + uh_is_initialized=0; + return; + } + TRACE("Started daemonized thread reading microHam\n"); +#endif +// if we do not have pthreads, this function does nothing. +} + +/* + * What comes now are "public" functions that can be called from outside + * + void uh_close_XXX() XXX= ptt, radio, wkey + void uh_open_XXX() XXX= ptt, wkey + void uh_open_radio(int baud, int databits, int stopbits, int rtscts) + void uh_set_ptt(int ptt) + int uh_get_ptt() + + * Note that it is not intended that any I/O is done via the PTT sockets + * but hamlib needs a valid file descriptor! + * + */ + +/* + * Close routines: + * Mark the channel as closed, but close the connection + * to the microHam device only if ALL channels are closed + * + * NOTE: hamlib repeatedly opens/closes the PTT port while keeping the + * the radio port open. + */ +void uh_close_ptt() +{ + uh_ptt_in_use=0; + if (!uh_radio_in_use && ! uh_wkey_in_use) { + close_microham(); + } +} + +void uh_close_radio() +{ + uh_radio_in_use=0; + if (!uh_ptt_in_use && ! uh_wkey_in_use) { + close_microham(); + } +} + +void uh_close_wkey() +{ + uh_wkey_in_use=0; + if (!uh_ptt_in_use && ! uh_radio_in_use) { + close_microham(); + } +} + +int uh_open_ptt() +{ + if (!uh_is_initialized) { + start_thread(); + } + if (!uh_is_initialized) { + return -1; + } + uh_ptt_in_use=1; + return uh_ptt_pair[1]; +} + +int uh_open_wkey() +{ + if (!uh_is_initialized) { + start_thread(); + } + if (!uh_is_initialized) { + return -1; + } + uh_wkey_in_use=1; + return uh_wkey_pair[1]; +} + +// +// Number of stop bits must be 1 or 2. +// Number of data bits can be 5,6,7,8 +// Hardware handshake (rtscts) can be on of off. +// microHam devices ALWAY use "no parity". +// +int uh_open_radio(int baud, int databits, int stopbits, int rtscts) +{ + unsigned char string[5]; + int baudrateConst; + + if (!uh_is_initialized) { + start_thread(); + } + if (!uh_is_initialized) { + return -1; + } + + baudrateConst = 11059200/baud ; + string[0] = 0x01; + string[1] = baudrateConst & 0xff ; + string[2] = baudrateConst/256 ; + switch (stopbits) { + case 1: string[3]=0x00; break; + case 2: string[3]=0x40; break; + default: return -1; + } + if (rtscts) { + string[3] |= 0x10; + } + switch (databits) { + case 5: break; + case 6: string[3] |= 0x20; break; + case 7: string[3] |= 0x40; break; + case 8: string[3] |= 0x60; break; + default: return -1; + } + string[4] = 0x81; + writeControl(string, 5); + + uh_radio_in_use=1; + return uh_radio_pair[1]; +} + +void uh_set_ptt(int ptt) +{ + if (!uh_ptt_in_use) { + ERROR("%10d:SetPTT but not open\n", TIME); + return; + } + DEBUG("%10d:SET PTT = %d\n", TIME, ptt); + if (ptt) { + statusbyte |= 0x04; + } else { + statusbyte &= ~0x04; + } + writeFlags(); +} + +int uh_get_ptt() +{ + if (statusbyte & 0x04) { + return 1; + } else { + return 0; + } +} diff --git a/src/microham.h b/src/microham.h new file mode 100644 index 000000000..36008fa06 --- /dev/null +++ b/src/microham.h @@ -0,0 +1,17 @@ +// +// Support for microHam +// +// Store the fd's of the sockets here. Then we can tell later on +// whether we are working on a "real" serial interface or on a socket +// + +int uh_ptt_fd = -1; // PUBLIC! must be visible in iofunc.c in WIN32 case +int uh_radio_fd = -1; // PUBLIC! must be visible in iofunc.c in WIN32 case + +extern int uh_open_radio(int baud, int databits, int stopbits, int rtscts); +extern int uh_open_ptt(); +extern void uh_set_ptt(int ptt); +extern int uh_get_ptt(); +extern void uh_close_radio(); +extern void uh_close_ptt(); + diff --git a/src/serial.c b/src/serial.c index 4fdcb2568..e7713527b 100644 --- a/src/serial.c +++ b/src/serial.c @@ -82,6 +82,7 @@ #include #endif +#include "microham.h" /** * \brief Open serial port using rig.state data @@ -100,6 +101,48 @@ int HAMLIB_API serial_open(hamlib_port_t *rp) return -RIG_EINVAL; } + if (!strncmp(rp->pathname,"uh-rig",6)) { + /* + * If the pathname is EXACTLY "uh-rig", try to use a microHam device + * rather than a conventional serial port. + * The microHam devices ALWAYS use "no parity", and can either use no handshake + * or hardware handshake. Return with error if something else is requested. + */ + if (rp->parm.serial.parity != RIG_PARITY_NONE) { + return -RIG_EIO; + } + if ((rp->parm.serial.handshake != RIG_HANDSHAKE_HARDWARE) && + (rp->parm.serial.handshake != RIG_HANDSHAKE_NONE)) { + return -RIG_EIO; + } + /* + * Note that serial setup is also don in uh_open_radio. + * So we need to dig into serial_setup(). + */ + fd=uh_open_radio( rp->parm.serial.rate, // baud + rp->parm.serial.data_bits, // databits + rp->parm.serial.stop_bits, // stopbits + (rp->parm.serial.handshake == RIG_HANDSHAKE_HARDWARE)); // rtscts + if (fd == -1) { + return -RIG_EIO; + } + rp->fd=fd; + /* + * Remember the fd in a global variable. We can do read(), write() and select() + * on fd but whenever it is tried to do an ioctl(), we have to catch it + * (e.g. setting DTR or tcflush on this fd does not work) + * While this may look dirty, it is certainly easier and more efficient than + * to check whether fd corresponds to a serial line or a socket everywhere. + * + * CAVEAT: for WIN32, it might be necessary to use win_serial_read() instead + * of read() for serial lines in iofunc.c. Therefore, we have to + * export uh_radio_fd to iofunc.c because in the case of sockets, + * read() must be used also in the WIN32 case. + * This is why uh_radio_fd is declared globally in microham.h. + */ + uh_radio_fd=fd; + return RIG_OK; + } /* * Open in Non-blocking mode. Watch for EAGAIN errors! */ @@ -449,8 +492,19 @@ int HAMLIB_API serial_flush(hamlib_port_t *p) { rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + char buf[32]; + /* + * Catch microHam case: + * if fd corresponds to a microHam device drain the line + * (which is a socket) by reading until it is empty. + */ + while (read(p->fd, buf, 32) > 0) { + /* do nothing */ + } + return RIG_OK; + } tcflush(p->fd, TCIFLUSH); - return RIG_OK; } @@ -462,9 +516,37 @@ int HAMLIB_API serial_flush(hamlib_port_t *p) */ int ser_open(hamlib_port_t *p) { + int ret; + rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); - return (p->fd = OPEN(p->pathname, O_RDWR | O_NOCTTY | O_NDELAY)); + if (!strncmp(p->pathname,"uh-rig",6)) { + /* + * This should not happen: ser_open is only used for + * DTR-only serial ports (ptt_pathname != rig_pathname). + */ + ret=-1; + } else { + if (!strncmp(p->pathname,"uh-ptt",6)) { + /* + * Use microHam device for doing PTT. Although a valid file + * descriptor is returned, it is not used for anything + * but must be remembered in a global variable: + * If it is tried later to set/unset DTR on this fd, we know + * that we cannot use ioctl and must rather call our + * PTT set/unset service routine. + */ + ret=uh_open_ptt(); + uh_ptt_fd=ret; + } else { + /* + * pathname is not uh_rig or uh_ptt: simply open() + */ + ret=OPEN(p->pathname, O_RDWR | O_NOCTTY | O_NDELAY); + } + } + p->fd=ret; + return ret; } @@ -475,9 +557,30 @@ int ser_open(hamlib_port_t *p) */ int ser_close(hamlib_port_t *p) { + int rc; + rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); - int rc = CLOSE(p->fd); + /* + * For microHam devices, do not close the + * socket via close but call a service routine + * (which might decide to keep the socket open). + * However, unset p->fd and uh_ptt_fd/uh_radio_fd. + */ + if (p->fd == uh_ptt_fd) { + uh_close_ptt(); + uh_ptt_fd=-1; + p->fd = -1; + return 0; + } + if (p->fd == uh_radio_fd) { + uh_close_radio(); + uh_radio_fd=-1; + p->fd = -1; + return 0; + } + + rc = CLOSE(p->fd); p->fd = -1; return rc; } @@ -498,6 +601,11 @@ int HAMLIB_API ser_set_rts(hamlib_port_t *p, int state) rig_debug(RIG_DEBUG_VERBOSE, "%s: RTS=%d\n", __func__, state); + // ignore this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return RIG_OK; + } + #if defined(TIOCMBIS) && defined(TIOCMBIC) rc = IOCTL(p->fd, state ? TIOCMBIS : TIOCMBIC, &y); #else @@ -539,6 +647,11 @@ int HAMLIB_API ser_get_rts(hamlib_port_t *p, int *state) rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // cannot do this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return -RIG_ENIMPL; + } + retcode = IOCTL(p->fd, TIOCMGET, &y); *state = (y & TIOCM_RTS) == TIOCM_RTS; @@ -561,6 +674,16 @@ int HAMLIB_API ser_set_dtr(hamlib_port_t *p, int state) rig_debug(RIG_DEBUG_VERBOSE, "%s: DTR=%d\n", __func__, state); + // silently ignore on microHam RADIO channel, + // but (un)set ptt on microHam PTT channel. + if (p->fd == uh_radio_fd) { + return RIG_OK; + } + if (p->fd == uh_ptt_fd) { + uh_set_ptt(state); + return RIG_OK; + } + #if defined(TIOCMBIS) && defined(TIOCMBIC) rc = IOCTL(p->fd, state ? TIOCMBIS : TIOCMBIC, &y); #else @@ -602,6 +725,15 @@ int HAMLIB_API ser_get_dtr(hamlib_port_t *p, int *state) rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // cannot do this for the RADIO port, return PTT state for the PTT port + if (p->fd == uh_ptt_fd) { + *state=uh_get_ptt(); + return RIG_OK; + } + if (p->fd == uh_radio_fd) { + return -RIG_ENIMPL; + } + retcode = IOCTL(p->fd, TIOCMGET, &y); *state = (y & TIOCM_DTR) == TIOCM_DTR; @@ -619,6 +751,11 @@ int HAMLIB_API ser_set_brk(hamlib_port_t *p, int state) { rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // ignore this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return RIG_OK; + } + #if defined(TIOCSBRK) && defined(TIOCCBRK) return IOCTL(p->fd, state ? TIOCSBRK : TIOCCBRK, 0) < 0 ? -RIG_EIO : RIG_OK; @@ -640,6 +777,11 @@ int HAMLIB_API ser_get_car(hamlib_port_t *p, int *state) rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // cannot do this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return -RIG_ENIMPL; + } + retcode = IOCTL(p->fd, TIOCMGET, &y); *state = (y & TIOCM_CAR) == TIOCM_CAR; @@ -659,6 +801,11 @@ int HAMLIB_API ser_get_cts(hamlib_port_t *p, int *state) rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // cannot do this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return -RIG_ENIMPL; + } + retcode = IOCTL(p->fd, TIOCMGET, &y); *state = (y & TIOCM_CTS) == TIOCM_CTS; @@ -678,6 +825,10 @@ int HAMLIB_API ser_get_dsr(hamlib_port_t *p, int *state) rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + // cannot do this for microHam ports + if (p->fd == uh_ptt_fd || p->fd == uh_radio_fd) { + return -RIG_ENIMPL; + } retcode = IOCTL(p->fd, TIOCMGET, &y); *state = (y & TIOCM_DSR) == TIOCM_DSR; diff --git a/tests/rigctl.1 b/tests/rigctl.1 index 70a910f1d..df3a0bb2c 100644 --- a/tests/rigctl.1 +++ b/tests/rigctl.1 @@ -52,7 +52,8 @@ for NET rigctl (rigctld). Use \fIdevice\fP as the file name of the port the radio is connected. Often a serial port, but could be a USB to serial adapter. Typically /dev/ttyS0, /dev/ttyS1, /dev/ttyUSB0, etc. on Linux or COM1, COM2, etc. -on Win32. +on Win32. The special string 'uh\-rig' may be given to enable micro-ham +device support. .TP .B \-p, --ptt-file=device Use \fIdevice\fP as the file name of the Push-To-Talk device using a @@ -198,26 +199,26 @@ Example: > V VFOB F 14200000 M CW 500 # set rig > v f m # query rig > .EOF. - + $ rigctl -m1 - pause 30 # wait for action to complete > get_pos # query rotator > .EOF. - + $ rotctl -m1 -