direwolf/ptt.c

691 lines
16 KiB
C
Raw Permalink Normal View History

Version 1.0 - Initial commit Changes to be committed: new file: .gitattributes new file: .gitignore new file: APRStt-Implementation-Notes.pdf new file: CHANGES.txt new file: LICENSE-dire-wolf.txt new file: LICENSE-other.txt new file: Makefile.linux new file: Makefile.win new file: Quick-Start-Guide-Windows.pdf new file: Raspberry-Pi-APRS.pdf new file: User-Guide.pdf new file: aclients.c new file: aprs_tt.c new file: aprs_tt.h new file: atest.c new file: audio.c new file: audio.h new file: audio_win.c new file: ax25_pad.c new file: ax25_pad.h new file: beacon.c new file: beacon.h new file: config.c new file: config.h new file: decode_aprs.c new file: decode_aprs.h new file: dedupe.c new file: dedupe.h new file: demod.c new file: demod.h new file: demod_9600.c new file: demod_9600.h new file: demod_afsk.c new file: demod_afsk.h new file: digipeater.c new file: digipeater.h new file: direwolf.c new file: direwolf.conf new file: direwolf.desktop new file: direwolf.h new file: dsp.c new file: dsp.h new file: dtmf.c new file: dtmf.h new file: dw-icon.ico new file: dw-icon.png new file: dw-icon.rc new file: dw-start.sh new file: dwgps.c new file: dwgps.h new file: encode_aprs.c new file: encode_aprs.h new file: fcs_calc.c new file: fcs_calc.h new file: fsk_demod_agc.h new file: fsk_demod_state.h new file: fsk_filters.h new file: fsk_gen_filter.h new file: gen_packets.c new file: gen_tone.c new file: gen_tone.h new file: hdlc_rec.c new file: hdlc_rec.h new file: hdlc_rec2.c new file: hdlc_rec2.h new file: hdlc_send.c new file: hdlc_send.h new file: igate.c new file: igate.h new file: kiss.c new file: kiss.h new file: kiss_frame.c new file: kiss_frame.h new file: kissnet.c new file: kissnet.h new file: latlong.c new file: latlong.h new file: ll2utm.c new file: misc/README-dire-wolf.txt new file: misc/strcasestr.c new file: misc/strsep.c new file: misc/strtok_r.c new file: morse.c new file: multi_modem.c new file: multi_modem.h new file: ptt.c new file: ptt.h new file: pttest.c new file: rdq.c new file: rdq.h new file: redecode.c new file: redecode.h new file: regex/COPYING new file: regex/INSTALL new file: regex/LICENSES new file: regex/NEWS new file: regex/README new file: regex/README-dire-wolf.txt new file: regex/re_comp.h new file: regex/regcomp.c new file: regex/regex.c new file: regex/regex.h new file: regex/regex_internal.c new file: regex/regex_internal.h new file: regex/regexec.c new file: rrbb.c new file: rrbb.h new file: server.c new file: server.h new file: symbols-new.txt new file: symbols.c new file: symbols.h new file: symbolsX.txt new file: textcolor.c new file: textcolor.h new file: tocalls.txt new file: tq.c new file: tq.h new file: tt_text.c new file: tt_text.h new file: tt_user.c new file: tt_user.h new file: tune.h new file: udp_test.c new file: utm/LatLong-UTMconversion.c new file: utm/LatLong-UTMconversion.h new file: utm/README.txt new file: utm/SwissGrid.cpp new file: utm/UTMConversions.cpp new file: utm/constants.h new file: utm2ll.c new file: version.h new file: xmit.c new file: xmit.h
2015-07-27 00:35:07 +00:00
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011,2013 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/*------------------------------------------------------------------
*
* Module: ptt.c
*
* Purpose: Activate the push to talk (PTT) signal to turn on transmitter.
*
* Description: Traditionally this is done with the RTS signal of the serial port.
*
* If we have two radio channels and only one serial port, DTR
* can be used for the second channel.
*
* If __WIN32__ is defined, we use the Windows interface.
* Otherwise we use the unix version suitable for either Cygwin or Linux.
*
* Version 0.9: Add ability to use GPIO pins on Linux.
*
* References: http://www.robbayer.com/files/serial-win.pdf
*
* https://www.kernel.org/doc/Documentation/gpio.txt
*
*---------------------------------------------------------------*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/time.h>
#if __WIN32__
#include <windows.h>
#else
#include <sys/termios.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
/* So we can have more common code for fd. */
typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)
#endif
#include "direwolf.h"
#include "textcolor.h"
#include "audio.h"
#include "ptt.h"
#if __WIN32__
#define RTS_ON(fd) EscapeCommFunction(fd,SETRTS);
#define RTS_OFF(fd) EscapeCommFunction(fd,CLRRTS);
#define DTR_ON(fd) EscapeCommFunction(fd,SETDTR);
#define DTR_OFF(fd) EscapeCommFunction(fd,CLRDTR);
#else
#define RTS_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
#define RTS_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
#define DTR_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); }
#define DTR_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); }
#endif
/*-------------------------------------------------------------------
*
* Name: ptt_init
*
* Purpose: Open serial port(s) used for PTT signals and set to proper state.
*
* Inputs: modem - Structure with communication parameters.
*
*
* Outputs: Remember required information for future use.
*
* Description:
*
*--------------------------------------------------------------------*/
static int ptt_num_channels;
static ptt_method_t ptt_method[MAX_CHANS]; /* Method for PTT signal. */
/* PTT_METHOD_NONE - not configured. Could be using VOX. */
/* PTT_METHOD_SERIAL - serial (com) port. */
/* PTT_METHOD_GPIO - general purpose I/O. */
static char ptt_device[MAX_CHANS][20]; /* Name of serial port device. */
/* e.g. COM1 or /dev/ttyS0. */
static ptt_line_t ptt_line[MAX_CHANS]; /* RTS or DTR when using serial port. */
static int ptt_gpio[MAX_CHANS]; /* GPIO number. Only used for Linux. */
/* Valid only when ptt_method is PTT_METHOD_GPIO. */
static int ptt_invert[MAX_CHANS]; /* Invert the signal. */
/* Normally higher voltage means transmit. */
static HANDLE ptt_fd[MAX_CHANS]; /* Serial port handle or fd. */
/* Could be the same for two channels */
/* if using both RTS and DTR. */
void ptt_init (struct audio_s *p_modem)
{
int ch;
HANDLE fd;
#if __WIN32__
#else
int using_gpio;
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ptt_init ( ... )\n");
#endif
/*
* First copy everything from p_modem to local variables
* so it is available for later use.
*
* Maybe all the PTT stuff should have its own structure.
*/
ptt_num_channels = p_modem->num_channels;
assert (ptt_num_channels >= 1 && ptt_num_channels <= MAX_CHANS);
for (ch=0; ch<ptt_num_channels; ch++) {
ptt_method[ch] = p_modem->ptt_method[ch];
strcpy (ptt_device[ch], p_modem->ptt_device[ch]);
ptt_line[ch] = p_modem->ptt_line[ch];
ptt_gpio[ch] = p_modem->ptt_gpio[ch];
ptt_invert[ch] = p_modem->ptt_invert[ch];
ptt_fd[ch] = INVALID_HANDLE_VALUE;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, invert=%d\n",
ch,
ptt_method[ch],
ptt_device[ch],
ptt_line[ch],
ptt_gpio[ch],
ptt_invert[ch]);
#endif
}
/*
* Set up serial ports.
*/
for (ch=0; ch<ptt_num_channels; ch++) {
if (ptt_method[ch] == PTT_METHOD_SERIAL) {
#if __WIN32__
#else
/* Translate Windows device name into Linux name. */
/* COM1 -> /dev/ttyS0, etc. */
if (strncasecmp(ptt_device[ch], "COM", 3) == 0) {
int n = atoi (ptt_device[ch] + 3);
text_color_set(DW_COLOR_INFO);
dw_printf ("Converted PTT device '%s'", ptt_device[ch]);
if (n < 1) n = 1;
sprintf (ptt_device[ch], "/dev/ttyS%d", n-1);
dw_printf (" to Linux equivalent '%s'\n", ptt_device[ch]);
}
#endif
/* Can't open the same device more than once so we */
/* need more logic to look for the case of both radio */
/* channels using different pins of the same COM port. */
/* TODO: Needs to be rewritten in a more general manner */
/* if we ever have more than 2 channels. */
if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) {
fd = ptt_fd[0];
}
else {
#if __WIN32__
fd = CreateFile(ptt_device[ch],
GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
#else
/* O_NONBLOCK added in version 0.9. */
/* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */
fd = open (ptt_device[ch], O_RDONLY | O_NONBLOCK);
#endif
}
if (fd != INVALID_HANDLE_VALUE) {
ptt_fd[ch] = fd;
}
else {
#if __WIN32__
#else
int e = errno;
#endif
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR can't open device %s for channel %d PTT control.\n",
ptt_device[ch], ch);
#if __WIN32__
#else
dw_printf ("%s\n", strerror(errno));
#endif
/* Don't try using it later if device open failed. */
ptt_method[ch] = PTT_METHOD_NONE;
}
/*
* Set initial state of PTT off.
* ptt_set will invert output signal if appropriate.
*/
ptt_set (ch, 0);
} /* if serial method. */
} /* For each channel. */
/*
* Set up GPIO - for Linux only.
*/
#if __WIN32__
#else
/*
* Does any channel use GPIO?
*/
using_gpio = 0;
for (ch=0; ch<ptt_num_channels; ch++) {
if (ptt_method[ch] == PTT_METHOD_GPIO) {
using_gpio = 1;
}
}
if (using_gpio) {
struct stat finfo;
/*
* Normally the device nodes are set up for access
* only by root. Try to change it here so we don't
* burden user with another configuration step.
*
* Does /sys/class/gpio/export even exist?
*/
if (stat("/sys/class/gpio/export", &finfo) < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/*
* Do we have permission to access it?
*
* pi@raspberrypi /sys/class/gpio $ ls -l
* total 0
* --w------- 1 root root 4096 Aug 20 07:59 export
* lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
* --w------- 1 root root 4096 Aug 20 07:59 unexport
*/
if (geteuid() != 0) {
if ( ! (finfo.st_mode & S_IWOTH)) {
/* Try to change protection. */
system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
if (stat("/sys/class/gpio/export", &finfo) < 0) {
/* Unexpected because we could do it before. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/* Did we succeed in changing the protection? */
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1);
}
}
}
}
/*
* We should now be able to create the device nodes for
* the pins we want to use.
*/
for (ch=0; ch<ptt_num_channels; ch++) {
if (ptt_method[ch] == PTT_METHOD_GPIO) {
char stemp[80];
struct stat finfo;
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1);
}
sprintf (stemp, "%d", ptt_gpio[ch]);
if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
int e = errno;
/* Ignore EBUSY error which seems to mean */
/* the device node already exists. */
if (e != EBUSY) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e);
dw_printf ("%s\n", strerror(e));
exit (1);
}
}
close (fd);
/*
* We will have the same permission problem if not root.
* We only care about "direction" and "value".
*/
sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
system (stemp);
sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
system (stemp);
sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
if (stat(stemp, &finfo) < 0) {
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to get status for %s \n", stemp);
dw_printf ("%s\n", strerror(e));
exit (1);
}
if (geteuid() != 0) {
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type these commands:\n");
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
exit (1);
}
}
/*
* Set output direction with initial state off.
*/
sprintf (stemp, "/sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
fd = open(stemp, O_WRONLY);
if (fd < 0) {
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error opening %s\n", stemp);
dw_printf ("%s\n", strerror(e));
exit (1);
}
char hilo[8];
if (ptt_invert[ch]) {
strcpy (hilo, "high");
}
else {
strcpy (hilo, "low");
}
if (write (fd, hilo, strlen(hilo)) != strlen(hilo)) {
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing initial state to %s\n", stemp);
dw_printf ("%s\n", strerror(e));
exit (1);
}
close (fd);
}
}
#endif
/* Why doesn't it transmit? Probably forgot to specify PTT option. */
for (ch=0; ch<ptt_num_channels; ch++) {
if(ptt_method[ch] == PTT_METHOD_NONE) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Note: PTT not configured for channel %d.\n", ch);
}
}
} /* end ptt_init */
/*-------------------------------------------------------------------
*
* Name: ptt_set
*
* Purpose: Turn transmitter on or off.
*
* Inputs: chan channel, 0 .. (number of channels)-1
*
* ptt 1 for transmit, 0 for receive.
*
*
* Assumption: ptt_init was called first.
*
* Description: Set the RTS or DTR line or GPIO pin.
* More positive output means transmit unless invert is set.
*
*--------------------------------------------------------------------*/
void ptt_set (int chan, int ptt)
{
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ptt_set ( %d, %d )\n", chan, ptt);
#endif
/*
* Inverted output?
*/
if (ptt_invert[chan]) {
ptt = ! ptt;
}
assert (chan >= 0 && chan < MAX_CHANS);
/*
* Using serial port?
*/
if (ptt_method[chan] == PTT_METHOD_SERIAL &&
ptt_fd[chan] != INVALID_HANDLE_VALUE) {
if (ptt_line[chan] == PTT_LINE_RTS) {
if (ptt) {
RTS_ON(ptt_fd[chan]);
}
else {
RTS_OFF(ptt_fd[chan]);
}
}
else if (ptt_line[chan] == PTT_LINE_DTR) {
if (ptt) {
DTR_ON(ptt_fd[chan]);
}
else {
DTR_OFF(ptt_fd[chan]);
}
}
}
/*
* Using GPIO?
*/
#if __WIN32__
#else
if (ptt_method[chan] == PTT_METHOD_GPIO) {
int fd;
char stemp[80];
sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[chan]);
fd = open(stemp, O_WRONLY);
if (fd < 0) {
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error opening %s to set PTT signal.\n", stemp);
dw_printf ("%s\n", strerror(e));
return;
}
sprintf (stemp, "%d", ptt);
if (write (fd, stemp, 1) != 1) {
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error setting GPIO %d for PTT\n", ptt_gpio[chan]);
dw_printf ("%s\n", strerror(e));
}
close (fd);
}
#endif
} /* end ptt_set */
/*-------------------------------------------------------------------
*
* Name: ptt_term
*
* Purpose: Make sure PTT is turned off when we exit.
*
* Inputs: none
*
* Description:
*
*--------------------------------------------------------------------*/
void ptt_term (void)
{
int n;
for (n = 0; n < ptt_num_channels; n++) {
ptt_set (n, 0);
if (ptt_fd[n] != INVALID_HANDLE_VALUE) {
#if __WIN32__
CloseHandle (ptt_fd[n]);
#else
close(ptt_fd[n]);
#endif
ptt_fd[n] = INVALID_HANDLE_VALUE;
}
}
}
/*
* Quick stand-alone test for above.
*
* gcc -DTEST -o ptest ptt.c ; ./ptest
*
*/
#if TEST
void text_color_set (dw_color_t c) { }
main ()
{
struct audio_s modem;
int n;
int chan;
memset (&modem, 0, sizeof(modem));
modem.num_channels = 2;
modem.ptt_method[0] = PTT_METHOD_SERIAL;
//strcpy (modem.ptt_device[0], "COM1");
strcpy (modem.ptt_device[0], "/dev/ttyUSB0");
modem.ptt_line[0] = PTT_LINE_RTS;
modem.ptt_method[1] = PTT_METHOD_SERIAL;
//strcpy (modem.ptt_device[1], "COM1");
strcpy (modem.ptt_device[1], "/dev/ttyUSB0");
modem.ptt_line[1] = PTT_LINE_DTR;
/* initialize - both off */
ptt_init (&modem);
SLEEP_SEC(2);
/* flash each a few times. */
dw_printf ("turn on RTS a few times...\n");
chan = 0;
for (n=0; n<3; n++) {
ptt_set (chan, 1);
SLEEP_SEC(1);
ptt_set (chan, 0);
SLEEP_SEC(1);
}
dw_printf ("turn on DTR a few times...\n");
chan = 1;
for (n=0; n<3; n++) {
ptt_set (chan, 1);
SLEEP_SEC(1);
ptt_set (chan, 0);
SLEEP_SEC(1);
}
ptt_term();
/* Same thing again but invert RTS. */
modem.ptt_invert[0] = 1;
ptt_init (&modem);
SLEEP_SEC(2);
dw_printf ("INVERTED - RTS a few times...\n");
chan = 0;
for (n=0; n<3; n++) {
ptt_set (chan, 1);
SLEEP_SEC(1);
ptt_set (chan, 0);
SLEEP_SEC(1);
}
dw_printf ("turn on DTR a few times...\n");
chan = 1;
for (n=0; n<3; n++) {
ptt_set (chan, 1);
SLEEP_SEC(1);
ptt_set (chan, 0);
SLEEP_SEC(1);
}
ptt_term ();
/* Test GPIO */
#if __WIN32__
#else
memset (&modem, 0, sizeof(modem));
modem.num_channels = 1;
modem.ptt_method[0] = PTT_METHOD_GPIO;
modem.ptt_gpio[0] = 25;
dw_printf ("Try GPIO %d a few times...\n", modem.ptt_gpio[0]);
ptt_init (&modem);
SLEEP_SEC(2);
chan = 0;
for (n=0; n<3; n++) {
ptt_set (chan, 1);
SLEEP_SEC(1);
ptt_set (chan, 0);
SLEEP_SEC(1);
}
ptt_term ();
#endif
}
#endif
/* end ptt.c */