/*
 *  Hamlib Interface - Microham device support for POSIX systems
 *  Copyright (c) 2017 by Christoph van Wüllen
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

//
// This file contains the support for microHam devices
//
// On WIN32, this behaves as if no microHam device is connected
// to the computer since (at least my version of MinGW) does not
// support socketpair().
//

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>

#ifdef HAVE_ERRNO_H
#  include <errno.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#  include <sys/select.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#  include <sys/socket.h>
#endif

//#define FRAME(s, ...)   printf(s, ##__VA_ARGS__)
//#define DEBUG(s, ...)   printf(s, ##__VA_ARGS__)
//#define TRACE(s, ...)   printf(s, ##__VA_ARGS__)
//#define MYERROR(s, ...) printf(s, ##__VA_ARGS__)

#ifndef FRAME
#  define FRAME(s, ...)
#endif

#ifndef DEBUG
#  define DEBUG(s, ...)
#endif

#ifndef TRACE
#  define TRACE(s, ...)
#endif

#ifndef MYERROR
#  define MYERROR(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

#if !(defined(WIN32) || !defined(HAVE_GLOB_H))
static char
uh_device_path[PATH_MAX];  // use PATH_MAX since udev names can be VERY long!
#endif
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 <pthread.h>

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

#if defined(HAVE_SELECT)
//
// time of last heartbeat. Updated by heartbeat()
//
static time_t lastbeat = 0;
#endif

#if defined(HAVE_PTHREAD) && defined(HAVE_SOCKETPAIR) && defined(HAVE_SELECT)
static time_t starttime;

#define TIME ((int) (time(NULL) - starttime))
#endif

//
// 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) || !defined(HAVE_GLOB_H)
/*
 * 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.
 *
 * 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.
 *
 *
 * For those who want to implement the WIN32 case here properly:
 *
 * 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 with correct serial speed etc. and put a valid fd into uh_device_fd.
 */
/* Commenting out the following dummy function to quell the warning from
 * MinGW's GCC of a defined but not used function.
 */
/* 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-<SERIAL> where <SERIAL> 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.
 *
 * Note: StationMaster uses a different protocol, and
 *       the protocol of StationMaster DeLuxe is not
 *       even disclosed.
 *
 * We are using the glob() function to obtain a list
 * of candidates.
 */

#define NUMUHTYPES 9
static struct uhtypes
{
    const char *name;
    const char *device;
} uhtypes[NUMUHTYPES] =
{

#ifdef __APPLE__
    { "microKeyer",      "/dev/tty.usbserial-MK*"},
    { "microKeyer-II",   "/dev/tty.usbserial-M2*"},
    { "microKeyer-III",  "/dev/tty.usbserial-M3*"},
    { "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*"},
    { "microKeyer-III",  "/dev/serial/by-id/*microHAM*_M3*"},
    { "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 <termios.h>
#include <sys/stat.h>
#include <glob.h>
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 < NUMUHTYPES; i++)
    {
        DEBUG("Checking for %s device\n", uhtypes[i].device);
        glob(uhtypes[i].device, 0, NULL, &gbuf);

        for (j = 0; j < gbuf.gl_pathc; j++)
        {
            DEBUG("Found file: %s\n", gbuf.gl_pathv[j]);

            if (!stat(gbuf.gl_pathv[j], &st))
            {
                if (S_ISCHR(st.st_mode))
                {
                    // found a character special device with correct name
                    if (strlen(gbuf.gl_pathv[j]) >= PATH_MAX)
                    {
                        // I do not know if this can happen, but if it happens, we just skip the device.
                        MYERROR("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)
                    {
                        MYERROR("Cannot open serial port %s\n", uh_device_path);
                        perror("Open:");
                        continue;
                    }

                    tcflush(fd, TCIFLUSH);

                    if (tcgetattr(fd, &TTY))
                    {
                        MYERROR("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))
                    {
                        MYERROR("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 successful, we skip all what might come
                    return;
                }
            }
        }
    }
}

#endif

#if defined(HAVE_SELECT)
//
// 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)
{
    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)
        {
            MYERROR("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:  // Flag byte
            DEBUG("%10d:RCV: Flags=%02x\n", TIME, byte);
            // No reason to pass the flags to clients
            break;

        case 1:  // part of control string
            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)
            {
                int i;
                // end of a control sequence
                controlstring[numcontrolbytes++] = byte;
                DEBUG("%10d:FromControl:", TIME);

                for (i = 0; i < numcontrolbytes; i++) { DEBUG(" %02x", controlstring[i]); }

                DEBUG(".\n");
                incontrol = 0;
                // printing control messages is only used for debugging.
                // Note that we can get a lot of unsolicited control messages
                // here (squelch, voltage change, etc.)
                break;
            }

            // in the middle of a control string
            controlstring[numcontrolbytes++] = byte;
            break;

        case 2: // message from WinKey chip
            DEBUG("%10d:RCV: WinKey=%02x\n", TIME, byte);

            if (write(uh_wkey_pair[0], &byte, 1) != 1)
            {
                MYERROR("Write Winkey socket\n");
            }

            break;

        case 3: // Key pressed on PS2 keyboard connected to microHam device
            DEBUG("%10d:RCV: PS2=%02x\n", TIME, byte);
            break;
        }
    }
}
#endif  /* HAVE_SELECT */


#if defined(HAVE_SELECT)
//
// Send radio bytes to microHam device
//
static void writeRadio(unsigned char *bytes, int len)
{
    unsigned char seq[4];
    int i;

    DEBUG("%10d:Send radio data: ", TIME);

    for (i = 0; i < len; i++) { DEBUG(" %02x", (int) bytes[i]); }

    DEBUG(".\n");

    getlock();

    for (i = 0; i < len; i++)
    {
        int ret;

        seq[0] = 0x28;
        seq[1] = 0x80 | bytes[i];
        seq[2] = 0x80;
        seq[3] = 0X80 | statusbyte;

        if (statusbyte & 0x80)
        {
            seq[0] |= 0x01;
        }

        if (bytes[i]   & 0x80)
        {
            seq[0] |= 0x04;
        }

        if ((ret = write(uh_device_fd, seq, 4)) < 4)
        {
            MYERROR("WriteRadio failed with %d\n", ret);

            if (ret < 0)
            {
                perror("WriteRadioError:");
            }
        }
    }

    freelock();
}
#endif  /* HAVE_SELECT */


//
// send statusbyte to microHam device
//
static void writeFlags()
{
    unsigned char seq[4];
    int ret;

    DEBUG("%10d:Sending FlagByte: %02x\n", TIME, statusbyte);
    getlock();
    seq[0] = 0x08;

    if (statusbyte & 0x80)
    {
        seq[0] = 0x09;
    }

    seq[1] = 0x80;
    seq[2] = 0x80;
    seq[3] = 0x80 | statusbyte;

    if ((ret = write(uh_device_fd, seq, 4)) < 4)
    {
        MYERROR("WriteFlags failed with %d\n", ret);

        if (ret < 0)
        {
            perror("WriteFlagsError:");
        }
    }

    freelock();
}


#if defined(HAVE_SELECT)
//
// Send bytes to the WinKeyer within microHam device
//
static void writeWkey(unsigned char *bytes, int len)
{
    unsigned char seq[12];
    int i;
    DEBUG("%10d:Send WinKey data: ", TIME);

    for (i = 0; i < len; i++) { DEBUG(" %02x", (int) bytes[i]); }

    DEBUG(".\n");
    // Winkey data is in the third frame of a sequence,
    // So send two no-ops first. Include statusbyte in first frame
    getlock();

    for (i = 0; i < len; i++)
    {
        int ret;

        seq[ 0] = 0x08;
        seq[ 1] = 0x80;
        seq[ 2] = 0x80;
        seq[ 3] = 0X80 | statusbyte;
        seq[ 4] = 0x40;
        seq[ 5] = 0x80;
        seq[ 6] = 0x80;
        seq[ 7] = 0x80;
        seq[ 8] = 0x48;
        seq[ 9] = 0x80;
        seq[10] = 0x80;
        seq[11] = 0x80 | bytes[i];

        if (statusbyte & 0x80)
        {
            seq[ 0] |= 0x01;
        }

        if (bytes[i]   & 0x80)
        {
            seq[ 8] |= 0x01;
        }

        if ((ret = write(uh_device_fd, seq, 12)) < 12)
        {
            MYERROR("WriteWINKEY failed with %d\n", ret);

            if (ret < 0)
            {
                perror("WriteWinkeyError:");
            }
        }
    }

    freelock();
}
#endif  /* HAVE_SELECT */


//
// Write a control string to the microHam device
//
static void writeControl(unsigned char *data, int len)
{
    int i;
    unsigned char seq[8];

    DEBUG("%10d:WriteControl:", TIME);

    for (i = 0; i < len; i++) { DEBUG(" %02x", data[i]); }

    DEBUG(".\n");
    // Control data is in the second frame of a sequence,
    // So send a no-op first. Include statusbyte in first frame.
    // First and last byte of the control message is NOT marked "valid"
    getlock();

    for (i = 0; i < len; i++)
    {
        int ret;

        // encode statusbyte in first frame
        seq[0] = 0x08;
        seq[1] = 0x80;
        seq[2] = 0x80;
        seq[3] = 0x80 | statusbyte;
        seq[4] = 0x48; // marked valid
        seq[5] = 0x80;
        seq[6] = 0x80;
        seq[7] = 0x80 | data[i];

        if (statusbyte & 0x80)
        {
            seq[0] |= 1;
        }

        if (i == 0 || i == len - 1)
        {
            seq[4] = 0x40; // un-mark valid
        }

        if (data[i] & 0x80)
        {
            seq[4] |= 0x01;
        }

        if ((ret = write(uh_device_fd, seq, 8)) < 8)
        {
            MYERROR("WriteControl failed, ret=%d\n", ret);

            if (ret < 0)
            {
                perror("WriteControlError:");
            }
        }
    }

    freelock();
}


#if defined(HAVE_PTHREAD) && defined(HAVE_SOCKETPAIR) && defined(HAVE_SELECT)
//
// send a heartbeat and record time
// The "last heartbeat" time is recorded in a global variable
// such that the service thread can decice whether a new
// heartbeat is due.
//
static void heartbeat()
{
    unsigned char seq[2];

    seq[0] = 0x7e;
    seq[1] = 0xfe;
    writeControl(seq, 2);
    lastbeat = time(NULL);
}
#endif /* defined(HAVE_PTHREAD) && defined(HAVE_SOCKETPAIR) && defined(HAVE_SELECT) */


#if defined(HAVE_SELECT)
//
// This thread reads from the microHam device and puts data on the sockets
// it also issues periodic heartbeat messages
// it also reads the sockets if data is available
//
static void *read_device(void *p)
{
    unsigned char frame[4];
    int framepos = 0;
    unsigned char buf[2];
    fd_set fds;
    struct timeval tv;

    // the bytes from the microHam decive come in "frames"
    // a frame is a four-byte sequence. The first byte has the MSB unset,
    // then come three bytes with the MSB set
    // What comes here is an "infinite" loop. However this thread
    // terminates if the device is closed.
    for (;;)
    {
        int ret;
        int maxdev;

        //
        // setting uh_is_initialized to zero in the main thread
        // tells this one that it is all over now
        //
        if (!uh_is_initialized)
        {
            return NULL;
        }


#if defined(HAVE_PTHREAD) && defined(HAVE_SOCKETPAIR) && defined(HAVE_SELECT)

        //
        // This is the right place to ensure that a heartbeat is sent
        // to the microham device regularly (15 sec delay is the maximum
        // allowed, let us use 5 secs to be on the safe side).
        //
        if ((time(NULL) - lastbeat) > 5)
        {
            heartbeat();
        }

#endif

        //
        // 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)
                {
                    MYERROR("FrameSyncStartError\n");
                    framepos = 0;
                }

                if ((buf[0] & 0x80) && framepos == 0)
                {
                    MYERROR("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);
            }
        }
    }

//    return NULL;
}
#endif


/*
 * 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.
 *
 * If we do not have socketpair(), the same thing applies.
 *
 * If we do not have select(), then the read thread cannot work so we
 * do not spawn it.
 */
static void start_thread()
{
#if defined(HAVE_PTHREAD) && defined(HAVE_SOCKETPAIR) && defined(HAVE_SELECT)
    /*
     * 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)
    {
        MYERROR("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)
    {
        MYERROR("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 ALWAYS 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)
    {
        MYERROR("%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()
{
    // Possibly we can do better, but we just reflect
    // what we have done via uh_set_ptt.
    if (statusbyte & 0x04)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
