383 lines
9.2 KiB
C
Executable File
383 lines
9.2 KiB
C
Executable File
#include <Python.h> // used by quisk.h
|
|
#include <complex.h> // Used by quisk.h
|
|
#include "quisk.h"
|
|
|
|
// This module provides methods to access the state of the key.
|
|
// First call quisk_open_key(name) to choose a method and initialize.
|
|
// Subsequent key access uses the method chosen.
|
|
|
|
static int key_is_down = 0; // internal key state
|
|
|
|
// Changes for MacOS support thanks to Mario, DL3LSM.
|
|
#if defined(MS_WINDOWS) || defined(__MACH__)
|
|
|
|
int quisk_open_key(const char * name)
|
|
{ // Open the hardware key; return 0 for success, else an error code.
|
|
return 0;
|
|
}
|
|
|
|
void quisk_close_key(void)
|
|
{
|
|
}
|
|
|
|
int quisk_is_key_down(void)
|
|
{
|
|
return key_is_down;
|
|
}
|
|
|
|
void quisk_set_key_down(int state)
|
|
{ // Set the key state internally
|
|
if (state)
|
|
key_is_down = 1;
|
|
else
|
|
key_is_down = 0;
|
|
}
|
|
#else
|
|
// Not MS Windows:
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#ifdef __linux__
|
|
#include <linux/ppdev.h>
|
|
#endif
|
|
#ifdef __FreeBSD__
|
|
#include <dev/ppbus/ppi.h>
|
|
#include <dev/ppbus/ppbconf.h>
|
|
#endif
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
|
|
static int open_key_pport(const char * name);
|
|
static int open_key_serport(const char * name);
|
|
static int open_key_enet(const char * name);
|
|
static void close_key_pport(void);
|
|
static void close_key_serport(void);
|
|
static void close_key_enet(void);
|
|
static int is_key_down_pport(void);
|
|
static int is_key_down_serport(void);
|
|
static int is_key_down_enet(void);
|
|
#if defined(ENABLE_GPIO_KEYER)
|
|
/* KC4UPR: Headers for new functions to implement the GPIO Keyer. They
|
|
* are not defined as static here, because I implemented the functions
|
|
* in a separate source file (gpiokeyer.c).
|
|
*/
|
|
int open_key_gpiokeyer(const char * name);
|
|
void close_key_gpiokeyer(void);
|
|
int is_key_down_gpiokeyer(void);
|
|
#endif
|
|
|
|
static enum { // The key access method
|
|
None, // Return the internal state; default key is always up
|
|
ParPort, // Use the parallel port
|
|
SerPort, // Use the serial port
|
|
Udp // Use UDP Ethernet
|
|
#if defined(ENABLE_GPIO_KEYER)
|
|
/* KC4UPR: Define a new hardware keyer type: a GPIO Keyer that
|
|
* implements straight, bug, and iambic keyer functionality, can be
|
|
* configured to use different pins on a Raspberry Pi.
|
|
*/
|
|
, GpioKeyer // Use Raspberry Pi GPIO keyer based on N1GP code
|
|
#endif
|
|
} key_method = None;
|
|
|
|
static int fd = -1; // File descriptor to read the parallel or serial port
|
|
static int KEY_PORT = 0x553C; // Ethernet UDP port
|
|
static int key_socket = -1; // Ethernet socket
|
|
|
|
int quisk_open_key(const char * name)
|
|
{ // Open the hardware key; return 0 for success, else an error code.
|
|
int ret;
|
|
|
|
if (!name[0]){ // null string means internal key state
|
|
key_method = None;
|
|
ret = 0;
|
|
}
|
|
else if (!strncmp(name, "/dev/tty", 8)){ // serial port
|
|
key_method = SerPort;
|
|
ret = open_key_serport(name);
|
|
}
|
|
else if (name[0] == '/'){ // starting '/' means parallel port name
|
|
key_method = ParPort;
|
|
ret = open_key_pport(name);
|
|
}
|
|
else if (isdigit(name[0])){ // IP address
|
|
key_method = Udp;
|
|
ret = open_key_enet(name);
|
|
}
|
|
#if defined(ENABLE_GPIO_KEYER)
|
|
/* KC4UPR: Check if the GPIO Keyer was requested, and if so, open
|
|
* it. Its name must start with "gpio". The overall format is:
|
|
* gpio:left,right,keyer_out,tr_switch
|
|
* where:
|
|
* left - GPIO input pin for the left paddle
|
|
* right - GPIO input pin for the right paddle
|
|
* keyer_out - GPIO output pin for the key (actual CW) signal
|
|
* (unlike the tr_switch line, this is only keyed
|
|
* for the actual dits and dahs)
|
|
* (set to zero to not use this)
|
|
* tr_switch - GPIO output pin for T/R switching (this is
|
|
* enabled any time keyer_out is enabled, plus a
|
|
* short, configurable delay after the last time
|
|
* the keyer output was set)
|
|
* (set to zero to not use this)
|
|
* NOTE: The pin numbers to be used are GPIO pin numbers, NOT
|
|
* WiringPi pin numbers even though WiringPi is used!!!
|
|
*/
|
|
else if (!strncmp(name, "gpio", 4)){ // Raspberry Pi GPIO keyer
|
|
key_method = GpioKeyer;
|
|
ret = open_key_gpiokeyer(name);
|
|
}
|
|
#endif
|
|
else {
|
|
ret = 5;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void quisk_close_key(void)
|
|
{
|
|
switch(key_method) {
|
|
case None:
|
|
break;
|
|
case ParPort:
|
|
close_key_pport();
|
|
break;
|
|
case SerPort:
|
|
close_key_serport();
|
|
break;
|
|
case Udp:
|
|
close_key_enet();
|
|
break;
|
|
#if defined(ENABLE_GPIO_KEYER)
|
|
/* KC4UPR: Self-explanatory--close the GPIO Keyer.
|
|
*/
|
|
case GpioKeyer:
|
|
close_key_gpiokeyer();
|
|
break;
|
|
#endif
|
|
}
|
|
return;
|
|
}
|
|
|
|
int quisk_is_key_down(void)
|
|
{
|
|
switch(key_method) {
|
|
case None:
|
|
return key_is_down;
|
|
case SerPort:
|
|
return is_key_down_serport();
|
|
case ParPort:
|
|
return is_key_down_pport();
|
|
case Udp:
|
|
return is_key_down_enet();
|
|
#if defined(ENABLE_GPIO_KEYER)
|
|
/* KC4UPR: Check if the GPIO Keyer key is down. This also checks
|
|
* the state of the internal key. I included this to allow use of
|
|
* the quisk_set_key_down() function in conjunction with the GPIO
|
|
* Keyer, for example in the case of digimodes. This could probably
|
|
* also reasonably be applied to the other hardware key methods
|
|
* above, but I decided to try to restrict my changes to things
|
|
* explicitly involving the GPIO Keyer.
|
|
*/
|
|
case GpioKeyer:
|
|
return is_key_down_gpiokeyer() | key_is_down;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void quisk_set_key_down(int state)
|
|
{ // Set the key state internally
|
|
if (state)
|
|
key_is_down = 1;
|
|
else
|
|
key_is_down = 0;
|
|
}
|
|
|
|
// ***************************************************
|
|
// Access the parallel port
|
|
static int open_key_pport(const char * name)
|
|
{
|
|
int byte;
|
|
|
|
if (fd >= 0)
|
|
close(fd);
|
|
fd = open(name, O_RDONLY);
|
|
if (fd == -1) {
|
|
printf("Open %s failed, try modprobe ppdev.\n", name);
|
|
}
|
|
#ifdef __linux__
|
|
else if (ioctl (fd, PPCLAIM)) {
|
|
perror ("PPCLAIM");
|
|
close (fd);
|
|
fd = -1;
|
|
}
|
|
#endif
|
|
else {
|
|
byte = 0x0;
|
|
#if defined(__linux__)
|
|
ioctl(fd, PPWCONTROL, &byte);
|
|
#endif
|
|
return 0; // Success
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void close_key_pport(void)
|
|
{
|
|
int byte;
|
|
|
|
if (fd >= 0) {
|
|
#ifdef __linux__
|
|
byte = 0x0;
|
|
ioctl(fd, PPWCONTROL, &byte);
|
|
#endif
|
|
close(fd);
|
|
}
|
|
fd = -1;
|
|
}
|
|
|
|
// This code writes to the control register so the PC can send a signal.
|
|
// Currently unused.
|
|
// ioctl(fd, PPRCONTROL, &byte);
|
|
// byte |= 0x02;
|
|
// ioctl(fd, PPWCONTROL, &byte);
|
|
|
|
static int is_key_down_pport(void)
|
|
{
|
|
#if defined(__linux__)
|
|
int byte;
|
|
if (fd < 0) // port not open
|
|
return 0; // Key is up
|
|
byte = 0;
|
|
ioctl(fd, PPRSTATUS, &byte);
|
|
if (byte & 0x10)
|
|
return 1; // Key is down
|
|
#elif defined(__FreeBSD__)
|
|
uint8_t byte;
|
|
if (fd < 0) // port not open
|
|
return 0; // Key is up
|
|
byte = 0;
|
|
ioctl(fd, PPIGSTATUS, &byte);
|
|
if (byte & 0x10)
|
|
return 1; // Key is down
|
|
#endif
|
|
return 0; // Key is up
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************
|
|
// Access using Ethernet
|
|
// Check for a UDP packet from the network to determine key status
|
|
|
|
static int open_key_enet(const char * ip)
|
|
{
|
|
struct sockaddr_in Addr;
|
|
|
|
close_key_enet();
|
|
key_socket = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if (key_socket < 0)
|
|
return -1;
|
|
memset(&Addr, 0, sizeof(Addr)); // Assign an address to our socket
|
|
Addr.sin_family = AF_INET;
|
|
Addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
Addr.sin_port = htons(KEY_PORT);
|
|
if (bind(key_socket, (struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
|
|
close_key_enet();
|
|
return -1;
|
|
}
|
|
memset(&Addr, 0, sizeof(Addr)); // Only accept UDP from this host
|
|
Addr.sin_family = AF_INET;
|
|
inet_aton(ip, &Addr.sin_addr);
|
|
Addr.sin_port = htons(KEY_PORT);
|
|
if (connect(key_socket, (struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
|
|
close_key_enet();
|
|
return -1;
|
|
}
|
|
return 0; // Success
|
|
}
|
|
|
|
static void close_key_enet(void)
|
|
{
|
|
if (key_socket != -1) {
|
|
shutdown(key_socket, SHUT_RDWR);
|
|
close(key_socket);
|
|
key_socket = -1;
|
|
}
|
|
}
|
|
|
|
static int is_key_down_enet(void)
|
|
{
|
|
static int keyed = 0;
|
|
unsigned char buf[4];
|
|
|
|
if (key_socket >= 0 && recv(key_socket, buf, 2, MSG_DONTWAIT) == 2)
|
|
keyed = buf[0]; // new key state is available
|
|
return keyed; // return current key state
|
|
}
|
|
|
|
|
|
// ***************************************************
|
|
// Access the serial port. This code sets DTR high, and monitors DSR.
|
|
// When DSR is high the key is down (else up).
|
|
// Set the RTS signal high when the key is down; else low after a delay.
|
|
static int open_key_serport(const char * name)
|
|
{
|
|
int bits;
|
|
|
|
if (fd >= 0)
|
|
close(fd);
|
|
fd = open(name, O_RDWR | O_NOCTTY);
|
|
if (fd == -1) {
|
|
printf("Open serial port %s failed.\n", name);
|
|
return -1;
|
|
}
|
|
ioctl(fd, TIOCMGET, &bits); // read modem bits
|
|
bits |= TIOCM_DTR; // Set DTR
|
|
bits &= ~TIOCM_RTS; // Clear RTS at first
|
|
ioctl(fd, TIOCMSET, &bits);
|
|
return 0; // Success
|
|
}
|
|
|
|
static void close_key_serport(void)
|
|
{
|
|
if (fd >= 0)
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
|
|
// Delay clearing RTS when the key goes up (semi breakin)
|
|
#define KEY_UP_DELAY_SECS 1.5
|
|
static int is_key_down_serport(void)
|
|
{
|
|
int bits;
|
|
struct timeval tv;
|
|
double time;
|
|
static double time0=0; // time when the key was last down
|
|
|
|
if (fd < 0) // Port not open
|
|
return 0; // Key is up
|
|
|
|
gettimeofday(&tv, NULL);
|
|
time = tv.tv_sec + tv.tv_usec / 1.0E6; // time is in seconds
|
|
ioctl(fd, TIOCMGET, &bits); // read modem bits
|
|
if (bits & TIOCM_DSR) { // Key is down
|
|
bits |= TIOCM_RTS; // Set RTS
|
|
ioctl(fd, TIOCMSET, &bits);
|
|
time0 = time;
|
|
return 1;
|
|
}
|
|
else { // Key is up
|
|
if (time - time0 > KEY_UP_DELAY_SECS) {
|
|
bits &= ~TIOCM_RTS; // Clear RTS after a delay
|
|
ioctl(fd, TIOCMSET, &bits);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|