//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2015 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: ttcalc.c
*
* Purpose: Simple Touch Tone to Speech calculator.
*
* Description: Demonstration of how Dire Wolf can be used
* as a DTMF / Speech interface for ham radio applications.
*
* Usage: Start up direwolf with configuration:
* - DTMF decoder enabled.
* - Text-to-speech enabled.
* - Listening to standard port 8000 for a client application.
*
* Run this in a different window.
*
* User sends formulas such as:
*
* 2 * 3 * 4 #
*
* with the touch tone pad.
* The result is sent back with speech, e.g. "Twenty Four."
*
*---------------------------------------------------------------*/
#if __WIN32__
#include
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
#include
#else
#include
#include
#include
#include
#include
#include
#include
#include
#endif
#include
#include
#include
#include
#include
#include "direwolf.h"
#include "ax25_pad.h"
#include "textcolor.h"
struct agwpe_s {
short portx; /* 0 for first, 1 for second, etc. */
short port_hi_reserved;
short kind_lo; /* message type */
short kind_hi;
char call_from[10];
char call_to[10];
int data_len; /* Number of data bytes following. */
int user_reserved;
};
static int calculator (char *str);
static int connect_to_server (char *hostname, char *port);
static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
/*------------------------------------------------------------------
*
* Name: main
*
*---------------------------------------------------------------*/
int main (int argc, char *argv[])
{
int server_sock = -1;
struct agwpe_s mon_cmd;
char data[1024];
char hostname[30] = "localhost";
char port[10] = "8000";
#if __WIN32__
#else
int err;
setlinebuf (stdout);
#endif
assert (calculator("12a34#") == 46);
assert (calculator("2*3A4#") == 10);
assert (calculator("5*100A3#") == 503);
assert (calculator("6a4*5#") == 50);
/*
* Try to attach to Dire Wolf.
*/
server_sock = connect_to_server (hostname, port);
if (server_sock == -1) {
exit (1);
}
/*
* Send command to toggle reception of frames in raw format.
*
* Note: Monitor format is only for UI frames.
*/
memset (&mon_cmd, 0, sizeof(mon_cmd));
mon_cmd.kind_lo = 'k';
#if __WIN32__
send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
/*
* Print all of the monitored packets.
*/
while (1) {
int n;
#if __WIN32__
n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
if (n != sizeof(mon_cmd)) {
printf ("Read error, received %d command bytes.\n", n);
exit (1);
}
assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data));
if (mon_cmd.data_len > 0) {
#if __WIN32__
n = recv (server_sock, data, mon_cmd.data_len, 0);
#else
n = read (server_sock, data, mon_cmd.data_len);
#endif
if (n != mon_cmd.data_len) {
printf ("Read error, client received %d data bytes when %d expected.\n", n, mon_cmd.data_len);
exit (1);
}
}
/*
* Print it.
*/
if (mon_cmd.kind_lo == 'K') {
packet_t pp;
char *pinfo;
int info_len;
char result[400];
char *p;
alevel_t alevel;
int chan;
chan = mon_cmd.portx;
memset (&alevel, 0, sizeof(alevel));
pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
ax25_format_addrs (pp, result);
info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
pinfo[info_len] = '\0';
strcat (result, pinfo);
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
printf ("[%d] %s\n", chan, result);
/*
* Look for Special touch tone packet with "t" in first position of the Information part.
*/
if (*pinfo == 't') {
int n;
char reply_text[200];
packet_t reply_pp;
struct {
struct agwpe_s hdr;
char extra;
unsigned char frame[AX25_MAX_PACKET_LEN];
} xmit_raw;
/*
* Send touch tone sequence to calculator and get the answer.
*
* Put your own application here instead. Here are some ideas:
*
* http://www.tapr.org/pipermail/aprssig/2015-January/044069.html
*/
n = calculator (pinfo+1);
printf ("\nCalculator returns %d\n\n", n);
/*
* Convert to AX.25 frame.
* Notice that the special destination will cause it to be spoken.
*/
sprintf (reply_text, "N0CALL>SPEECH:%d", n);
reply_pp = ax25_from_text(reply_text, 1);
/*
* Send it to the TNC.
* In this example we are transmitting speech on the same channel
* where the tones were heard. We could also send AX.25 frames to
* other radio channels.
*/
memset (&xmit_raw, 0, sizeof(xmit_raw));
xmit_raw.hdr.portx = chan;
xmit_raw.hdr.kind_lo = 'K';
xmit_raw.hdr.data_len = 1 + ax25_pack (reply_pp, xmit_raw.frame);
#if __WIN32__
send (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len, 0);
#else
err = write (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len);
#endif
ax25_delete (reply_pp);
}
ax25_delete (pp);
}
}
} /* main */
/*------------------------------------------------------------------
*
* Name: calculator
*
* Purpose: Simple calculator to demonstrate Touch Tone to Speech
* application tool kit.
*
* Inputs: str - Sequence of touch tone characters: 0-9 A-D * #
* It should be terminated with #.
*
* Returns: Numeric result of calculation.
*
* Description: This is a simple calculator that recognizes
* numbers,
* * for multiply
* A for add
* # for equals result
*
* Adding functions to B, C, and D is left as an
* exercise for the reader.
*
* Examples: 2 * 3 A 4 # Ten
* 5 * 1 0 0 A 3 # Five Hundred Three
*
*---------------------------------------------------------------*/
#define DO_LAST_OP \
switch (lastop) { \
case NONE: result = num; num = 0; break; \
case ADD: result += num; num = 0; break; \
case SUB: result -= num; num = 0; break; \
case MUL: result *= num; num = 0; break; \
case DIV: result /= num; num = 0; break; \
}
static int calculator (char *str)
{
int result;
int num;
enum { NONE, ADD, SUB, MUL, DIV } lastop;
char *p;
result = 0;
num = 0;
lastop = NONE;
for (p = str; *p != '\0'; p++) {
if (isdigit(*p)) {
num = num * 10 + *p - '0';
}
else if (*p == '*') {
DO_LAST_OP;
lastop = MUL;
}
else if (*p == 'A' || *p == 'a') {
DO_LAST_OP;
lastop = ADD;
}
else if (*p == '#') {
DO_LAST_OP;
return (result);
}
}
return (result); // not expected.
}
/*------------------------------------------------------------------
*
* Name: connect_to_server
*
* Purpose: Connect to Dire Wolf TNC server.
*
* Inputs: hostname
* port
*
* Returns: File descriptor or -1 for error.
*
*---------------------------------------------------------------*/
static int connect_to_server (char *hostname, char *port)
{
#if __WIN32__
#else
int e;
#endif
#define MAX_HOSTS 30
struct addrinfo hints;
struct addrinfo *ai_head = NULL;
struct addrinfo *ai;
struct addrinfo *hosts[MAX_HOSTS];
int num_hosts, n;
int err;
char ipaddr_str[46]; /* text form of IP address */
#if __WIN32__
WSADATA wsadata;
#endif
/*
* File descriptor for socket to server.
* Set to -1 if not connected.
* (Don't use SOCKET type because it is unsigned.)
*/
int server_sock = -1;
#if __WIN32__
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
printf("WSAStartup failed: %d\n", err);
exit (1);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
exit (1);
}
#endif
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
// hints.ai_family = AF_INET; /* IPv4 only. */
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/*
* Connect to specified hostname & port.
*/
ai_head = NULL;
err = getaddrinfo(hostname, port, &hints, &ai_head);
if (err != 0) {
#if __WIN32__
printf ("Can't get address for server %s, err=%d\n", hostname, WSAGetLastError());
#else
printf ("Can't get address for server %s, %s\n", hostname, gai_strerror(err));
#endif
freeaddrinfo(ai_head);
exit (1);
}
num_hosts = 0;
for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
// Try each address until we find one that is successful.
for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
#if __WIN32__
if (is == INVALID_SOCKET) {
printf ("Socket creation failed, err=%d", WSAGetLastError());
WSACleanup();
is = -1;
continue;
}
#else
if (err != 0) {
printf ("Socket creation failed, err=%s", gai_strerror(err));
(void) close (is);
is = -1;
continue;
}
#endif
err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
#if __WIN32__
if (err == SOCKET_ERROR) {
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
(void) close (is);
is = -1;
continue;
}
int flag = 1;
err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
if (err < 0) {
printf("setsockopt TCP_NODELAY failed.\n");
}
#endif
/*
* Success.
*/
printf("Client app now connected to %s (%s), port %s\n", hostname, ipaddr_str, port);
server_sock = is;
break;
}
freeaddrinfo(ai_head);
if (server_sock == -1) {
printf("Unnable to connect to %s (%s), port %s\n", hostname, ipaddr_str, port);
}
return (server_sock);
} /* end connect_to_server */
/*------------------------------------------------------------------
*
* Name: ia_to_text
*
* Purpose: Convert Internet address to text.
* Can't use InetNtop because it is supported only
* on Windows Vista and later.
*
*---------------------------------------------------------------*/
static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
{
struct sockaddr_in *sa4;
struct sockaddr_in6 *sa6;
switch (Family) {
case AF_INET:
sa4 = (struct sockaddr_in *)pAddr;
#if __WIN32__
sprintf (pStringBuf, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
sa4->sin_addr.S_un.S_un_b.s_b2,
sa4->sin_addr.S_un.S_un_b.s_b3,
sa4->sin_addr.S_un.S_un_b.s_b4);
#else
inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
#endif
break;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)pAddr;
#if __WIN32__
sprintf (pStringBuf, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
#else
inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
#endif
break;
default:
sprintf (pStringBuf, "Invalid address family!");
}
assert (strlen(pStringBuf) < StringBufSize);
return pStringBuf;
} /* end ia_to_text */
/* end ttcalc.c */