diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b13cb352..14313efc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,7 +85,9 @@ list(APPEND direwolf_SOURCES morse.c multi_modem.c waypoint.c + tnc_common.c nettnc.c + sertnc.c serial_port.c pfilter.c ptt.c diff --git a/src/audio.h b/src/audio.h index 27940865..e0a58c9d 100644 --- a/src/audio.h +++ b/src/audio.h @@ -60,7 +60,8 @@ typedef enum retry_e { enum medium_e { MEDIUM_NONE = 0, // Channel is not valid for use. MEDIUM_RADIO, // Internal modem for radio. MEDIUM_IGATE, // Access IGate as ordinary channel. - MEDIUM_NETTNC }; // Remote network TNC. (new in 1.8) + MEDIUM_NETTNC, // Remote network TNC. (new in 1.8) + MEDIUM_SERTNC }; // Local serial TNC. (new in 1.8) typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; @@ -152,6 +153,7 @@ struct audio_s { // MEDIUM_RADIO for internal modem. (only possibility earlier) // MEDIUM_IGATE allows application access to IGate. // MEDIUM_NETTNC for external TNC via TCP. + // MEDIUM_SERTNC for external TNC via serial port. int igate_vchannel; /* Virtual channel mapped to APRS-IS. */ /* -1 for none. */ @@ -164,6 +166,12 @@ struct audio_s { int nettnc_port[MAX_TOTAL_CHANS]; // Network TNC TCP port. + // Applies only to serial TNC type channels. + + char sertnc_device[MAX_TOTAL_CHANS][80]; // Serial TNC device name. + + int sertnc_baud[MAX_TOTAL_CHANS]; // Serial TNC baud rate. + /* Properties for each radio channel, common to receive and transmit. */ diff --git a/src/beacon.c b/src/beacon.c index b868f228..a518bdfe 100644 --- a/src/beacon.c +++ b/src/beacon.c @@ -165,7 +165,8 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct if (chan >= MAX_TOTAL_CHANS) chan = 0; // For ICHANNEL, use channel 0 call. if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO || - g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) { + g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC || + g_modem_config_p->chan_medium[chan] == MEDIUM_SERTNC) { if (strlen(g_modem_config_p->mycall[chan]) > 0 && strcasecmp(g_modem_config_p->mycall[chan], "N0CALL") != 0 && diff --git a/src/cdigipeater.c b/src/cdigipeater.c index 844af470..22bcba1c 100644 --- a/src/cdigipeater.c +++ b/src/cdigipeater.c @@ -134,7 +134,8 @@ void cdigipeater (int from_chan, packet_t pp) if ( from_chan < 0 || from_chan >= MAX_RADIO_CHANS || (save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO && - save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC) ) { + save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC && + save_audio_config_p->chan_medium[from_chan] != MEDIUM_SERTNC) ) { text_color_set(DW_COLOR_ERROR); dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan); return; diff --git a/src/config.c b/src/config.c index a1134bc5..876a9a6a 100644 --- a/src/config.c +++ b/src/config.c @@ -1381,6 +1381,70 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->nettnc_port[nchan] = atoi(t); } +/* + * SCHANNEL chan device baudrate - Define Serial TNC virtual channel. + * + * This allows a client application to talk to to an external TNC over serial KISS + * by using a channel number outside the normal range for modems. + * This does not change the current channel number used by MODEM, PTT, etc. + * + * chan = direwolf channel. + * device = device (serial port) name of serial TNC. + * baudrate = baud rate for communicating with serial TNC. + * + * Future: Might allow selection of channel on the serial TNC. + * For now, ignore incoming and set to 0 for outgoing. + * + * FIXME: Can't set mycall for schannel. + */ + + else if (strcasecmp(t, "SCHANNEL") == 0) { + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing virtual channel number for SCHANNEL command.\n", line); + continue; + } + int nchan = atoi(t); + if (nchan >= MAX_RADIO_CHANS && nchan < MAX_TOTAL_CHANS) { + + if (p_audio_config->chan_medium[nchan] == MEDIUM_NONE) { + + p_audio_config->chan_medium[nchan] = MEDIUM_SERTNC; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: SCHANNEL can't use channel %d because it is already in use.\n", line, nchan); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: SCHANNEL number must in range of %d to %d.\n", line, MAX_RADIO_CHANS, MAX_TOTAL_CHANS-1); + } + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing serial TNC device for SCHANNEL command.\n", line); + continue; + } + strlcpy (p_audio_config->sertnc_device[nchan], t, sizeof(p_audio_config->sertnc_device[nchan])); + int n; + t = split(NULL,0); + if (t != NULL) { + n = atoi(t); + if (n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200 && n != 38400 && n != 57600 && n != 115200) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Warning: Unsupported data rate of %d bits per second. Using 9600.\n", line, n); + n = 9600; + } + p_audio_config->sertnc_baud[nchan] = n; + } + else { + p_audio_config->sertnc_baud[nchan] = 9600; + } + } + /* * MYCALL station */ @@ -2749,7 +2813,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, // Channels specified must be radio channels or network TNCs. if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO && - p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC) { + p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC && + p_audio_config->chan_medium[from_chan] != MEDIUM_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); @@ -2777,7 +2842,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO && - p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC) { + p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC && + p_audio_config->chan_medium[to_chan] != MEDIUM_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); @@ -3111,7 +3177,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO && - p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC) { + p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC && + p_audio_config->chan_medium[from_chan] != MEDIUM_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); @@ -3149,7 +3216,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO && - p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC) { + p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC && + p_audio_config->chan_medium[to_chan] != MEDIUM_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); @@ -4429,7 +4497,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, x = -1; } else if (p_audio_config->chan_medium[x] != MEDIUM_RADIO && - p_audio_config->chan_medium[x] != MEDIUM_NETTNC) { + p_audio_config->chan_medium[x] != MEDIUM_NETTNC && + p_audio_config->chan_medium[x] != MEDIUM_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x); x = -1; @@ -5784,7 +5853,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* When IGate is enabled, all radio channels must have a callsign associated. */ if (strlen(p_igate_config->t2_login) > 0 && - (p_audio_config->chan_medium[i] == MEDIUM_RADIO || p_audio_config->chan_medium[i] == MEDIUM_NETTNC)) { + (p_audio_config->chan_medium[i] == MEDIUM_RADIO || + p_audio_config->chan_medium[i] == MEDIUM_NETTNC || + p_audio_config->chan_medium[i] == MEDIUM_SERTNC)) { if (strcmp(p_audio_config->mycall[i], "NOCALL") == 0 || strcmp(p_audio_config->mycall[i], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); @@ -5810,7 +5881,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (strlen(p_igate_config->t2_login) > 0) { for (j=0; jchan_medium[j] == MEDIUM_RADIO || p_audio_config->chan_medium[j] == MEDIUM_NETTNC) { + if (p_audio_config->chan_medium[j] == MEDIUM_RADIO || + p_audio_config->chan_medium[j] == MEDIUM_NETTNC || + p_audio_config->chan_medium[j] == MEDIUM_SERTNC) { if (p_digi_config->filter_str[MAX_TOTAL_CHANS][j] == NULL) { p_digi_config->filter_str[MAX_TOTAL_CHANS][j] = strdup("i/180"); } diff --git a/src/digipeater.c b/src/digipeater.c index fcf59568..a2992aed 100644 --- a/src/digipeater.c +++ b/src/digipeater.c @@ -156,7 +156,8 @@ void digipeater (int from_chan, packet_t pp) if ( from_chan < 0 || from_chan >= MAX_TOTAL_CHANS || (save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO && - save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC)) { + save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC && + save_audio_config_p->chan_medium[from_chan] != MEDIUM_SERTNC)) { text_color_set(DW_COLOR_ERROR); dw_printf ("APRS digipeater: Did not expect to receive on invalid channel %d.\n", from_chan); } diff --git a/src/direwolf.c b/src/direwolf.c index f89dc6e6..844f0cbc 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -131,6 +131,7 @@ #include "dlq.h" // for fec_type_t definition. #include "deviceid.h" #include "nettnc.h" +#include "sertnc.h" //static int idx_decoded = 0; @@ -1018,11 +1019,12 @@ int main (int argc, char *argv[]) il2p_init (d_2_opt); /* - * New in 1.8 - Allow a channel to be mapped to a network TNC rather than - * an internal modem and radio. + * New in 1.8 - Allow a channel to be mapped to a network or serial TNC rather + * than an internal modem and radio. * I put it here so channel properties would come out in right order. */ nettnc_init (&audio_config); + sertnc_init (&audio_config); /* * Initialize the touch tone decoder & APRStt gateway. @@ -1191,6 +1193,7 @@ int main (int argc, char *argv[]) * -1 for DTMF decoder. * -2 for channel mapped to APRS-IS. * -3 for channel mapped to network TNC. + * -4 for channel mapped to serial TNC. * slice - Slicer which caught it. * pp - Packet handle. * alevel - Audio level, range of 0 - 100. @@ -1221,7 +1224,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev // Can indicate FX.25/IL2P or fix_bits. assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels - assert (subchan >= -3 && subchan < MAX_SUBCHANS); + assert (subchan >= -4 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); assert (pp != NULL); // 1.1J+ @@ -1342,7 +1345,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev dw_printf ("Setting audio input level so most stations are around 50 will provide good dyanmic range.\n"); } // FIXME: rather than checking for ichannel, how about checking medium==radio - else if (alevel.rec < 5 && chan != audio_config.igate_vchannel && subchan != -3) { + else if (alevel.rec < 5 && chan != audio_config.igate_vchannel && subchan != SUBCHAN_NETTNC && subchan != SUBCHAN_SERTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input level is too low. Increase so most stations are around 50.\n"); @@ -1367,15 +1370,19 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev strlcpy (ts, "", sizeof(ts)); } - if (subchan == -1) { // dtmf + if (subchan == SUBCHAN_DTMF) { text_color_set(DW_COLOR_REC); dw_printf ("[%d.dtmf%s] ", chan, ts); } - else if (subchan == -2) { // APRS-IS + else if (subchan == SUBCHAN_APRSIS) { text_color_set(DW_COLOR_REC); dw_printf ("[%d.is%s] ", chan, ts); } - else if (subchan == -3) { // nettnc + else if (subchan == SUBCHAN_NETTNC) { + text_color_set(DW_COLOR_REC); + dw_printf ("[%d%s] ", chan, ts); + } + else if (subchan == SUBCHAN_SERTNC) { text_color_set(DW_COLOR_REC); dw_printf ("[%d%s] ", chan, ts); } diff --git a/src/dlq.h b/src/dlq.h index fdac1c0c..2b2a8a56 100644 --- a/src/dlq.h +++ b/src/dlq.h @@ -62,9 +62,13 @@ typedef struct dlq_item_s { int subchan; /* Winning "subchannel" when using multiple */ /* decoders on one channel. */ - /* Special case, -1 means DTMF decoder. */ /* Maybe we should have a different type in this case? */ +#define SUBCHAN_DTMF -1 +#define SUBCHAN_APRSIS -2 +#define SUBCHAN_NETTNC -3 +#define SUBCHAN_SERTNC -4 + int slice; /* Winning slicer. */ packet_t pp; /* Pointer to frame structure. */ diff --git a/src/nettnc.c b/src/nettnc.c index 72d18cc5..271a5b7b 100644 --- a/src/nettnc.c +++ b/src/nettnc.c @@ -61,12 +61,10 @@ #include "dlq.h" // received packet queue #include "nettnc.h" +#include "tnc_common.h" -void hex_dump (unsigned char *p, int len); - - // TODO: define macros in common locaation to hide platform specifics. #if __WIN32__ @@ -83,9 +81,8 @@ static pthread_t nettnc_listen_tid[MAX_TOTAL_CHANS]; static THREAD_F nettnc_listen_thread (void *arg); #endif -static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override); +static int s_kiss_debug = 0; -int s_kiss_debug = 0; /*------------------------------------------------------------------- @@ -285,7 +282,7 @@ static void * nettnc_listen_thread (void *arg) for (int j = 0; j < n; j++) { // Separate the byte stream into KISS frame(s) and make it // look like this came from a radio channel. - my_kiss_rec_byte (&kstate, buf[j], s_kiss_debug, chan); + my_kiss_rec_byte (&kstate, buf[j], s_kiss_debug, chan, SUBCHAN_NETTNC); } } // s_tnc_sock != -1 } // while (1) @@ -296,139 +293,6 @@ static void * nettnc_listen_thread (void *arg) -/*------------------------------------------------------------------- - * - * Name: my_kiss_rec_byte - * - * Purpose: Process one byte from a KISS network TNC. - * - * Inputs: kf - Current state of building a frame. - * b - A byte from the input stream. - * debug - Activates debug output. - * channel_overide - Set incoming channel number to the NCHANNEL - * number rather than the channel in the KISS frame. - * - * Outputs: kf - Current state is updated. - * - * Returns: none. - * - * Description: This is a simplified version of kiss_rec_byte used - * for talking to KISS client applications. It already has - * too many special cases and I don't want to make it worse. - * This also needs to make the packet look like it came from - * a radio channel, not from a client app. - * - *-----------------------------------------------------------------*/ - -static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override) -{ - - //dw_printf ("my_kiss_rec_byte ( %c %02x ) \n", b, b); - - switch (kf->state) { - - case KS_SEARCHING: /* Searching for starting FEND. */ - default: - - if (b == FEND) { - - /* Start of frame. */ - - kf->kiss_len = 0; - kf->kiss_msg[kf->kiss_len++] = b; - kf->state = KS_COLLECTING; - return; - } - return; - break; - - case KS_COLLECTING: /* Frame collection in progress. */ - - - if (b == FEND) { - - unsigned char unwrapped[AX25_MAX_PACKET_LEN]; - int ulen; - - /* End of frame. */ - - if (kf->kiss_len == 0) { - /* Empty frame. Starting a new one. */ - kf->kiss_msg[kf->kiss_len++] = b; - return; - } - if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { - /* Empty frame. Just go on collecting. */ - return; - } - - kf->kiss_msg[kf->kiss_len++] = b; - if (debug) { - /* As received over the wire from network TNC. */ - // May include escapted characters. What about FEND? -// FIXME: make it say Network TNC. - kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); - } - - ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); - - if (debug >= 2) { - /* Append CRC to this and it goes out over the radio. */ - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\n"); - dw_printf ("Frame content after removing KISS framing and any escapes:\n"); - /* Don't include the "type" indicator. */ - /* It contains the radio channel and type should always be 0 here. */ - hex_dump (unwrapped+1, ulen-1); - } - - // Convert to packet object and send to received packet queue. - // Note that we use channel associated with the network TNC, not channel in KISS frame. - - int subchan = -3; - int slice = 0; - alevel_t alevel; - memset(&alevel, 0, sizeof(alevel)); - packet_t pp = ax25_from_frame (unwrapped+1, ulen-1, alevel); - if (pp != NULL) { - fec_type_t fec_type = fec_type_none; - retry_t retries; - memset (&retries, 0, sizeof(retries)); - char spectrum[] = "Network TNC"; - dlq_rec_frame (channel_override, subchan, slice, pp, alevel, fec_type, retries, spectrum); - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Failed to create packet object for KISS frame from channel %d network TNC.\n", channel_override); - } - - kf->state = KS_SEARCHING; - return; - } - - if (kf->kiss_len < MAX_KISS_LEN) { - kf->kiss_msg[kf->kiss_len++] = b; - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("KISS frame from network TNC exceeded maximum length.\n"); - } - return; - break; - } - - return; /* unreachable but suppress compiler warning. */ - -} /* end my_kiss_rec_byte */ - - - - - - - - - /*------------------------------------------------------------------- * * Name: nettnc_send_packet diff --git a/src/sertnc.c b/src/sertnc.c new file mode 100644 index 00000000..dbe75785 --- /dev/null +++ b/src/sertnc.c @@ -0,0 +1,322 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2024 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 . +// + + + +/*------------------------------------------------------------------ + * + * Module: nettnc.c + * + * Purpose: Attach to Network KISS TNC(s) for NCHANNEL config file item(s). + * + * Description: Called once at application start up. + * + *---------------------------------------------------------------*/ + + +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + +#if __WIN32__ +#include +#include // _WIN32_WINNT must be set to 0x0501 before including this +#else +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "textcolor.h" +#include "audio.h" // configuration. +#include "kiss.h" +#include "ax25_pad.h" // for AX25_MAX_PACKET_LEN +#include "dlq.h" // received packet queue + +#include "serial_port.h" +#include "sertnc.h" +#include "tnc_common.h" + + + +// TODO: define macros in common locaation to hide platform specifics. + +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +#if __WIN32__ +static HANDLE sertnc_listen_th[MAX_TOTAL_CHANS]; +static THREAD_F sertnc_listen_thread (void *arg); +#else +static pthread_t sertnc_listen_tid[MAX_TOTAL_CHANS]; +static THREAD_F sertnc_listen_thread (void *arg); +#endif + +static int s_kiss_debug = 0; + + +/*------------------------------------------------------------------- + * + * Name: sertnc_init + * + * Purpose: Attach to Serial KISS TNC(s) for SCHANNEL config file item(s). + * + * Inputs: pa - Address of structure of type audio_s. + * + * debug ? TBD + * + * + * Returns: 0 for success, -1 for failure. + * + * Description: Called once at direwolf application start up time. + * Calls sertnc_attach for each SCHANNEL configuration item. + * + *--------------------------------------------------------------------*/ + +void sertnc_init (struct audio_s *pa) +{ + for (int i = 0; i < MAX_TOTAL_CHANS; i++) { + + if (pa->chan_medium[i] == MEDIUM_SERTNC) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: Serial TNC %s %d\n", i, pa->sertnc_device[i], pa->sertnc_baud[i]); + int e = sertnc_attach (i, pa->sertnc_device[i], pa->sertnc_baud[i]); + if (e < 0) { + exit (1); + } + } + } + +} // end nettnc_init + + + +/*------------------------------------------------------------------- + * + * Name: sertnc_attach + * + * Purpose: Attach to one Serial KISS TNC. + * + * Inputs: chan - channel number from SCHANNEL configuration. + * + * device - Serial device name. Something like "/dev/ttyS0" or "COM4". + * + * baud - Serial baud rate. Typically 9600. + * + * Returns: 0 for success, -1 for failure. + * + * Description: This starts up a thread, for each device, which listens to the port and + * dispatches the messages to the corresponding callback functions. + * It will also attempt to re-establish communication with the + * device if it goes away. + * + *--------------------------------------------------------------------*/ + +static char s_tnc_device[MAX_TOTAL_CHANS][80]; +static int s_tnc_baud[MAX_TOTAL_CHANS]; +static volatile MYFDTYPE s_tnc_fd[MAX_TOTAL_CHANS]; // File descriptor. MYFDERROR for invalid. + + +int sertnc_attach (int chan, char *device, int baud) +{ + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + strlcpy (s_tnc_device[chan], device, sizeof(s_tnc_device[chan])); + s_tnc_baud[chan] = baud; + s_tnc_fd[chan] = MYFDERROR; + + s_tnc_fd[chan] = serial_port_open (s_tnc_device[chan], s_tnc_baud[chan]); + + if (s_tnc_fd[chan] == MYFDERROR) { + return (-1); + } + + +/* + * Read frames from the serial TNC. + * If the TNC disappears, try to reestablish communication. + */ + + +#if __WIN32__ + sertnc_listen_th[chan] = (HANDLE)_beginthreadex (NULL, 0, sertnc_listen_thread, (void *)(ptrdiff_t)chan, 0, NULL); + if (sertnc_listen_th[chan] == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error: Could not create serial TNC listening thread\n"); + return (-1); + } +#else + int e = pthread_create (&sertnc_listen_tid[chan], NULL, sertnc_listen_thread, (void *)(ptrdiff_t)chan); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Internal error: Could not create serial TNC listening thread"); + return (-1); + } +#endif + +// TNC initialization if specified. + +// if (s_tnc_init_func != NULL) { +// e = (*s_tnc_init_func)(); +// return (e); +// } + + return (0); + +} // end sertnc_attach + + + +/*------------------------------------------------------------------- + * + * Name: sertnc_listen_thread + * + * Purpose: Listen for anything from TNC and process it. + * Reconnect if something goes wrong and we got disconnected. + * + * Inputs: arg - Channel number. + * s_tnc_device[chan] - Device & baud rate for re-connection. + * s_tnc_baud[chan] + * + * Outputs: s_tnc_fd[chan] - File descriptor for communicating with TNC. + * Will be MYFDERROR if not connected. + * + *--------------------------------------------------------------------*/ + +#if __WIN32__ +static unsigned __stdcall sertnc_listen_thread (void *arg) +#else +static void * sertnc_listen_thread (void *arg) +#endif +{ + int chan = (int)(ptrdiff_t)arg; + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + kiss_frame_t kstate; // State machine to gather a KISS frame. + memset (&kstate, 0, sizeof(kstate)); + + int ch; // normally 0-255 but -1 for error. + + while (1) { +/* + * Re-attach to TNC if not currently attached. + */ + if (s_tnc_fd[chan] == MYFDERROR) { + + text_color_set(DW_COLOR_ERROR); + // I'm using the term "attach" here, in an attempt to + // avoid confusion with the AX.25 connect. + dw_printf ("Attempting to reattach to serial TNC...\n"); + + s_tnc_fd[chan] = serial_port_open (s_tnc_device[chan], s_tnc_baud[chan]); + + if (s_tnc_fd[chan] != MYFDERROR) { + dw_printf ("Successfully reattached to serial TNC.\n"); + } + } + else { + ch = serial_port_get1 (s_tnc_fd[chan]); + + if (ch == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Lost communication with serial TNC. Will try to reattach.\n"); + serial_port_close (s_tnc_fd[chan]); + s_tnc_fd[chan] = MYFDERROR; + SLEEP_SEC(5); + continue; + } + +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("TEMP DEBUG: byte received from channel %d serial TNC.\n", chan); +#endif + // Separate the byte stream into KISS frame(s) and make it + // look like this came from a radio channel. + my_kiss_rec_byte (&kstate, ch, s_kiss_debug, chan, SUBCHAN_SERTNC); + } // s_tnc_fd != MYFDERROR + } // while (1) + + return (0); // unreachable but shutup warning. + +} // end sertnc_listen_thread + + + +/*------------------------------------------------------------------- + * + * Name: sertnc_send_packet + * + * Purpose: Send packet to a KISS serial TNC. + * + * Inputs: chan - Channel number from SCHANNEL configuration. + * pp - Packet object. + * + * Outputs: Packet is converted to KISS and send to serial TNC. + * + * Returns: none. + * + * Description: This does not free the packet object; caller is responsible. + * + *-----------------------------------------------------------------*/ + +void sertnc_send_packet (int chan, packet_t pp) +{ + +// First, get the on-air frame format from packet object. +// Prepend 0 byte for KISS command and channel. + + unsigned char frame_buff[AX25_MAX_PACKET_LEN + 2]; // One byte for channel/command, + // followed by the AX.25 on-air format frame. + frame_buff[0] = 0; // For now, set channel to 0. + + unsigned char *fbuf = ax25_get_frame_data_ptr (pp); + int flen = ax25_get_frame_len (pp); + + memcpy (frame_buff+1, fbuf, flen); + +// Next, encapsulate into KISS frame with surrounding FENDs and any escapes. + + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; + int kiss_len = kiss_encapsulate (frame_buff, flen+1, kiss_buff); + + int err = serial_port_write (s_tnc_fd[chan], (char*) kiss_buff, kiss_len); + if (err <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending packet to KISS Serial TNC for channel %d. Closing connection.\n\n", err, chan); + serial_port_close (s_tnc_fd[chan]); + s_tnc_fd[chan] = MYFDERROR; + } + + // Do not free packet object; caller will take care of it. + +} /* end nettnc_send_packet */ + diff --git a/src/sertnc.h b/src/sertnc.h new file mode 100644 index 00000000..4664f412 --- /dev/null +++ b/src/sertnc.h @@ -0,0 +1,7 @@ + + +void sertnc_init (struct audio_s *pa); + +int sertnc_attach (int chan, char *devicename, int baud); + +void sertnc_send_packet (int chan, packet_t pp); \ No newline at end of file diff --git a/src/server.c b/src/server.c index 64a99540..f859eb95 100644 --- a/src/server.c +++ b/src/server.c @@ -1569,7 +1569,8 @@ static THREAD_F cmd_listen_thread (void *arg) for (j=0; jchan_medium[j] == MEDIUM_RADIO || save_audio_config_p->chan_medium[j] == MEDIUM_IGATE || - save_audio_config_p->chan_medium[j] == MEDIUM_NETTNC) { + save_audio_config_p->chan_medium[j] == MEDIUM_NETTNC || + save_audio_config_p->chan_medium[j] == MEDIUM_SERTNC) { count++; } } @@ -1615,6 +1616,15 @@ static THREAD_F cmd_listen_thread (void *arg) } break; + case MEDIUM_SERTNC: + { + // could elaborate with device, etc. + char stemp[100]; + snprintf (stemp, sizeof(stemp), "Port%d Serial TNC;", j+1); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + break; + default: ; // Only list valid channels. break; diff --git a/src/tnc_common.c b/src/tnc_common.c new file mode 100644 index 00000000..9426b7cb --- /dev/null +++ b/src/tnc_common.c @@ -0,0 +1,185 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2024 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 . +// + + + +/*------------------------------------------------------------------ + * + * Module: tnc_common.c + * + * Purpose: Functions common to both network and serial TNCs. + * + *---------------------------------------------------------------*/ + + +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + +#if __WIN32__ +#include +#include // _WIN32_WINNT must be set to 0x0501 before including this +#else +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include "textcolor.h" +#include "kiss.h" +#include "dlq.h" // received packet queue + + + +void hex_dump (unsigned char *p, int len); + + +/*------------------------------------------------------------------- + * + * Name: my_kiss_rec_byte + * + * Purpose: Process one byte from a KISS network TNC. + * + * Inputs: kf - Current state of building a frame. + * b - A byte from the input stream. + * debug - Activates debug output. + * channel_overide - Set incoming channel number to the NCHANNEL + * or SCHANNEL number rather than the channel in + * the KISS frame. + * subchan - Sub-channel type, used here for identifying the frame + * as associated with either a network or a serial TNC. + * + * Outputs: kf - Current state is updated. + * + * Returns: none. + * + * Description: This is a simplified version of kiss_rec_byte used + * for talking to KISS client applications. It already has + * too many special cases and I don't want to make it worse. + * This also needs to make the packet look like it came from + * a radio channel, not from a client app. + * + *-----------------------------------------------------------------*/ + +void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override, int subchan) +{ + + //dw_printf ("my_kiss_rec_byte ( %c %02x ) \n", b, b); + + switch (kf->state) { + + case KS_SEARCHING: /* Searching for starting FEND. */ + default: + + if (b == FEND) { + + /* Start of frame. */ + + kf->kiss_len = 0; + kf->kiss_msg[kf->kiss_len++] = b; + kf->state = KS_COLLECTING; + return; + } + return; + break; + + case KS_COLLECTING: /* Frame collection in progress. */ + + + if (b == FEND) { + + unsigned char unwrapped[AX25_MAX_PACKET_LEN]; + int ulen; + + /* End of frame. */ + + if (kf->kiss_len == 0) { + /* Empty frame. Starting a new one. */ + kf->kiss_msg[kf->kiss_len++] = b; + return; + } + if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { + /* Empty frame. Just go on collecting. */ + return; + } + + kf->kiss_msg[kf->kiss_len++] = b; + if (debug) { + /* As received over the wire from network or serial TNC. */ + // May include escapted characters. What about FEND? +// FIXME: make it say Network TNC or Serial TNC. + kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); + } + + ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); + + if (debug >= 2) { + /* Append CRC to this and it goes out over the radio. */ + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + dw_printf ("Frame content after removing KISS framing and any escapes:\n"); + /* Don't include the "type" indicator. */ + /* It contains the radio channel and type should always be 0 here. */ + hex_dump (unwrapped+1, ulen-1); + } + + // Convert to packet object and send to received packet queue. + // Note that we use channel associated with the network or serial TNC, not channel in KISS frame. + + int slice = 0; + alevel_t alevel; + memset(&alevel, 0, sizeof(alevel)); + packet_t pp = ax25_from_frame (unwrapped+1, ulen-1, alevel); + if (pp != NULL) { + fec_type_t fec_type = fec_type_none; + retry_t retries; + memset (&retries, 0, sizeof(retries)); + char *spectrum = (subchan == SUBCHAN_NETTNC ? "Network TNC" : "Serial TNC"); + dlq_rec_frame (channel_override, subchan, slice, pp, alevel, fec_type, retries, spectrum); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to create packet object for KISS frame from channel %d %s TNC.\n", + channel_override, subchan == SUBCHAN_NETTNC ? "network" : "serial"); + } + + kf->state = KS_SEARCHING; + return; + } + + if (kf->kiss_len < MAX_KISS_LEN) { + kf->kiss_msg[kf->kiss_len++] = b; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("KISS frame from %s TNC exceeded maximum length.\n", + subchan == SUBCHAN_NETTNC ? "network" : "serial"); + } + return; + break; + } + + return; /* unreachable but suppress compiler warning. */ + +} /* end my_kiss_rec_byte */ + diff --git a/src/tnc_common.h b/src/tnc_common.h new file mode 100644 index 00000000..01d715a7 --- /dev/null +++ b/src/tnc_common.h @@ -0,0 +1 @@ +void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override, int subchan); diff --git a/src/tq.c b/src/tq.c index 0738eca1..cedd1b01 100644 --- a/src/tq.c +++ b/src/tq.c @@ -53,6 +53,7 @@ #include "igate.h" #include "dtime_now.h" #include "nettnc.h" +#include "sertnc.h" static packet_t queue_head[MAX_RADIO_CHANS][TQ_NUM_PRIO]; /* Head of linked list for each queue. */ @@ -261,7 +262,8 @@ void tq_append (int chan, int prio, packet_t pp) #ifndef DIGITEST // avoid dtest link error if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE || - save_audio_config_p->chan_medium[chan] == MEDIUM_NETTNC) { + save_audio_config_p->chan_medium[chan] == MEDIUM_NETTNC || + save_audio_config_p->chan_medium[chan] == MEDIUM_SERTNC) { char ts[100]; // optional time stamp. @@ -290,7 +292,7 @@ void tq_append (int chan, int prio, packet_t pp) igate_send_rec_packet (chan, pp); } - else { // network TNC + else if (save_audio_config_p->chan_medium[chan] == MEDIUM_NETTNC) { // network TNC dw_printf ("[%d>nt%s] ", chan, ts); dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); @@ -298,6 +300,15 @@ void tq_append (int chan, int prio, packet_t pp) nettnc_send_packet (chan, pp); + } + else if (save_audio_config_p->chan_medium[chan] == MEDIUM_SERTNC) { // serial TNC + dw_printf ("[%d>st%s] ", chan, ts); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); + dw_printf ("\n"); + + sertnc_send_packet (chan, pp); + } ax25_delete(pp);