diff --git a/README.adoc b/README.adoc
index cf0ba553..49732770 100644
--- a/README.adoc
+++ b/README.adoc
@@ -25,6 +25,7 @@ either C++ or TCL. Examples of modules are:
* *PropagationMonitor* -- Announce propagation warnings from dxmaps.com
* *SelCall* -- Send selective calling sequences by entering DTMF codes
* *MetarInformation* -- Play airport weather information
+* *Frn* -- Connect to Free Radio Network (FRN) servers
== Qtel ==
Qtel, the Qt EchoLink client, is a graphical application used to access the
diff --git a/src/doc/man/CMakeLists.txt b/src/doc/man/CMakeLists.txt
index b4b417ea..c89f4663 100644
--- a/src/doc/man/CMakeLists.txt
+++ b/src/doc/man/CMakeLists.txt
@@ -3,6 +3,7 @@ set(MAN_PAGES svxlink.1 remotetrx.1 siglevdetcal.1 svxlink.conf.5
ModuleHelp.conf.5 remotetrx.conf.5 ModuleParrot.conf.5 ModuleEchoLink.conf.5
ModuleTclVoiceMail.conf.5 ModuleDtmfRepeater.conf.5
ModulePropagationMonitor.conf.5 ModuleSelCallEnc.conf.5 qtel.1 devcal.1
+ ModuleFrn.conf.5
)
# Search for the gzip and groff programs. Error out if not found.
diff --git a/src/doc/man/ModuleFrn.conf.5 b/src/doc/man/ModuleFrn.conf.5
new file mode 100644
index 00000000..f10760bb
--- /dev/null
+++ b/src/doc/man/ModuleFrn.conf.5
@@ -0,0 +1,145 @@
+.TH MODULEFRN.CONF 5 "MARS 2013" Linux "File Formats"
+.
+.SH NAME
+.
+ModuleFrn.conf \- Configuration file for the SvxLink server
+Free Radio Network (FRN) module
+.
+.SH DESCRIPTION
+.
+.B svxlink
+is a general purpose voice service system for ham radio use. This man-page
+describe the SvxLink server configuration for the FRN module.
+.P
+The FRN module is used to connect to Free Radio Network (FRN) servers.
+.
+.SH CONFIGURATION VARIABLES
+.
+There are a couple of configuration variables that are common to all modules.
+The documentation for these can be found in the
+.BR svxlink.conf (5)
+manual page.
+.P
+Here is the description of all module specific configuration
+variables that the SvxLink FRN module understands.
+.
+.SS ModuleFrn
+.
+.TP
+.B SERVER
+Free Radio Network (FRN) server hostname or ip address, you can find a list
+of active servers using FRN desktop application or at
+http://freeradionetwork.eu web page.
+.TP
+.B PORT
+FRN server port number
+.TP
+.B VERSION
+Identify itself as a FRN client with this version.
+.TP
+.B EMAIL_ADDRESS
+Your email address, which was used during registration.
+.TP
+.B DYN_PASSWORD
+Password provided to you by FRN on registration.
+.TP
+.B CLIENT_TYPE
+Type of the client:
+.br
+0 - crosslink
+.br
+1 - gateway
+.br
+2 - pc only
+
+Read more at http://freeradionetwork.eu/frnprotocol.htm web page.
+.TP
+.B CALLSIGN_AND_USER
+Your callsign and user name used during registration, for example "1234, Bob".
+.TP
+.B BAND_AND_CHANNEL
+In case of PC only user, this is 'PC Only'.
+In case of a crosslink this is 'Crosslink'.
+In case of a gateway this is the frequancy in full Mhz and kHz seperated by a
+'.' (dot), followed by the mode 'FM', 'AM' or 'DIG', followed by a space and
+the squelch type 'CTC', 'DSC' or 'NONE' followed by the CTC frequency or ID
+code.
+
+Examples:
+.br
+446.03125FM CTC131.8
+.br
+28.12500AM
+.br
+145.43750FM DCS828
+.br
+446.02625DIG CID123
+.TP
+.B DESCRIPTION
+Custom extra information about the node.
+.TP
+.B COUNTRY
+Full name of the country.
+.TP
+.B CITY_CITY_PART
+The city and the part of the city, or the part of the country and the city
+divided by a minus sign, for example "City \- Street".
+.TP
+.B NET
+Network (room) where to connect, each server holds up to 32 networks (rooms).
+.TP
+.B FRN_DEBUG
+Define to enable extensive module logging, including FRN request and response
+printout.
+.TP
+.B DISABLE_RF
+Define to disable sending voice from FRN to RIG. Nothing will be transmitted
+by radio (could be switched ON by DTMF command later if needed)
+.
+.SH FRN NEW USER REGISTRATION
+To register at FRN service it is possible to use native desktop client or just
+register using netcat:
+
+echo 'IG:\\
+.br
+[CallsignAndUser]\\
+.br
+[EMailAddress]\\
+.br
+[BandAndChannel]\\
+.br
+[Description]\\
+.br
+[Country]\\
+.br
+[CityCityPart]' | nc -v sysman.freeradionetwork.eu 10025
+
+FRN System Manager should return OK in case of successful registration.
+
+More information about new user registration is available at
+http://freeradionetwork.eu/frnprotocol.htm
+.
+.SH FILES
+.
+.TP
+.IR /etc/svxlink/svxlink.conf " (or deprecated " /etc/svxlink.conf ")"
+The system wide configuration file.
+.TP
+.IR ~/.svxlink/svxlink.conf
+Per user configuration file.
+.TP
+.I /etc/svxlink/svxlink.d/ModuleFrn.conf
+Global modularized configuration file. Depends on the CFG_DIR configuration
+variable setting.
+.TP
+.I ~/.svxlink/svxlink.d/ModuleFrn.conf
+Per user modularized configuration file. Depends on the CFG_DIR configuration
+variable setting.
+.
+.SH AUTHOR
+.
+Tobias Blomberg (SM0SVX)
+.
+.SH "SEE ALSO"
+.
+.BR svxlink.conf (5)
diff --git a/src/svxlink/modules/CMakeLists.txt b/src/svxlink/modules/CMakeLists.txt
index 4bfb99f5..5872f446 100644
--- a/src/svxlink/modules/CMakeLists.txt
+++ b/src/svxlink/modules/CMakeLists.txt
@@ -8,3 +8,4 @@ add_subdirectory(dtmf_repeater)
add_subdirectory(metarinfo)
add_subdirectory(propagation_monitor)
add_subdirectory(selcallenc)
+add_subdirectory(frn)
diff --git a/src/svxlink/modules/frn/.gitignore b/src/svxlink/modules/frn/.gitignore
new file mode 100644
index 00000000..8c40ff28
--- /dev/null
+++ b/src/svxlink/modules/frn/.gitignore
@@ -0,0 +1,3 @@
+/makefile.root
+/depend
+/Makefile
diff --git a/src/svxlink/modules/frn/CMakeLists.txt b/src/svxlink/modules/frn/CMakeLists.txt
new file mode 100644
index 00000000..444c118d
--- /dev/null
+++ b/src/svxlink/modules/frn/CMakeLists.txt
@@ -0,0 +1,40 @@
+# The name of the module without the Module prefix
+set(MODNAME Frn)
+
+# Module source code
+set(MODSRC QsoFrn.cpp Utils.cpp)
+
+# Project libraries to link to
+#set(LIBS ${LIBS} echolib)
+
+# Find the GSM codec library and include directory
+find_package(GSM REQUIRED)
+if(NOT GSM_FOUND)
+ message(FATAL_ERROR "libgsm not found")
+endif(NOT GSM_FOUND)
+include_directories(${GSM_INCLUDE_DIR})
+set(LIBS ${LIBS} ${GSM_LIBRARY})
+
+# The version tag name without the VER_ prefix
+set(VERNAME ${MODNAME})
+string(REGEX REPLACE \(.\)\([A-Z]\) \\1_\\2 VERNAME ${VERNAME})
+string(TOUPPER MODULE_${VERNAME} VERNAME)
+
+# Add targets for version files
+set(VERSION_DEPENDS)
+add_version_target(${VERNAME} VERSION_DEPENDS)
+add_version_target(SVXLINK VERSION_DEPENDS)
+
+# Build the plugin
+add_library(Module${MODNAME} MODULE Module${MODNAME}.cpp ${MODSRC}
+ ${VERSION_DEPENDS}
+)
+set_target_properties(Module${MODNAME} PROPERTIES PREFIX "")
+target_link_libraries(Module${MODNAME} ${LIBS})
+
+# Install targets
+install(TARGETS Module${MODNAME} DESTINATION ${SVX_MODULE_INSTALL_DIR})
+install(FILES ${MODNAME}.tcl DESTINATION ${SVX_SHARE_INSTALL_DIR}/events.d)
+install_if_not_exists(Module${MODNAME}.conf
+ ${SVX_SYSCONF_INSTALL_DIR}/svxlink.d
+ )
diff --git a/src/svxlink/modules/frn/Frn.tcl b/src/svxlink/modules/frn/Frn.tcl
new file mode 100644
index 00000000..38e95869
--- /dev/null
+++ b/src/svxlink/modules/frn/Frn.tcl
@@ -0,0 +1,144 @@
+###############################################################################
+#
+# Frn module event handlers
+#
+###############################################################################
+
+#
+# This is the namespace in which all functions and variables below will exist.
+# The name must match the configuration variable "NAME" in the
+# [ModuleFrn] section in the configuration file. The name may be changed
+# but it must be changed in both places.
+#
+namespace eval Frn {
+
+#
+# Check if this module is loaded in the current logic core
+#
+if {![info exists CFG_ID]} {
+ return;
+}
+
+#
+# Extract the module name from the current namespace
+#
+set module_name [namespace tail [namespace current]];
+
+
+#
+# An "overloaded" playMsg that eliminates the need to write the module name
+# as the first argument.
+#
+proc playMsg {msg} {
+ variable module_name;
+ ::playMsg $module_name $msg;
+}
+
+
+#
+# A convenience function for printing out information prefixed by the
+# module name
+#
+proc printInfo {msg} {
+ variable module_name;
+ puts "$module_name: $msg";
+}
+
+
+#
+# Executed when this module is being activated
+#
+proc activating_module {} {
+ variable module_name;
+ Module::activating_module $module_name;
+}
+
+
+#
+# Executed when this module is being deactivated.
+#
+proc deactivating_module {} {
+ variable module_name;
+ Module::deactivating_module $module_name;
+}
+
+
+#
+# Executed when the inactivity timeout for this module has expired.
+#
+proc timeout {} {
+ variable module_name;
+ Module::timeout $module_name;
+}
+
+
+#
+# Executed when playing of the help message for this module has been requested.
+#
+proc play_help {} {
+ variable module_name;
+ Module::play_help $module_name;
+}
+
+
+#
+# Executed when the state of this module should be reported on the radio
+# channel. Typically this is done when a manual identification has been
+# triggered by the user by sending a "*".
+# This function will only be called if this module is active.
+#
+proc status_report {} {
+ printInfo "status_report called...";
+}
+
+
+#
+# Executed when an entered command failed or have bad syntax.
+#
+proc command_failed {cmd} {
+ spellWord $cmd;
+ playMsg "operation_failed";
+}
+
+
+#
+# Executed when an unrecognized command has been received.
+#
+proc unknown_command {cmd} {
+ spellWord $cmd;
+ playMsg "unknown_command";
+}
+
+
+#
+# Executed when command to count nodes on the channel is called
+#
+proc count_clients {count_clients} {
+ playNumber $count_clients;
+ playSilence 50;
+ playMsg "connected_clients";
+ playSilence 250;
+}
+
+
+#
+# Executed when the rf disable feature is activated or deactivated
+# status - The current status of the feature (0=deactivated, 1=activated)
+# activate - The requested new status of the feature
+# (0=deactivate, 1=activate)
+#
+proc rf_disable {status activate} {
+ variable module_name;
+ puts "$module_name: [expr {$activate ? "Activating" : "Deactivating"}]\
+ listen only mode.";
+ playMsg [expr {$activate ? "activating" : "deactivating"}];
+ playMsg "rf_disable";
+}
+
+# end of namespace
+}
+
+
+#
+# This file has not been truncated
+#
diff --git a/src/svxlink/modules/frn/ModuleFrn.conf b/src/svxlink/modules/frn/ModuleFrn.conf
new file mode 100644
index 00000000..2a39763d
--- /dev/null
+++ b/src/svxlink/modules/frn/ModuleFrn.conf
@@ -0,0 +1,22 @@
+[ModuleFrn]
+NAME=Frn
+PLUGIN_NAME=Frn
+ID=7
+TIMEOUT=300
+
+# Details http://freeradionetwork.eu/frnprotocol.htm
+SERVER=127.0.0.1
+PORT=10024
+VERSION=2014000
+EMAIL_ADDRESS=your@example.com
+DYN_PASSWORD=12345
+CLIENT_TYPE=1
+CALLSIGN_AND_USER="callsign, user"
+BAND_AND_CHANNEL="446.03125FM CTC131.8"
+DESCRIPTION="SvxLink FreeRadioNetwork Station"
+COUNTRY=Antarctica
+CITY_CITY_PART="City - Street"
+NET=Test
+
+#FRN_DEBUG=1
+#DISABLE_RF=1
diff --git a/src/svxlink/modules/frn/ModuleFrn.cpp b/src/svxlink/modules/frn/ModuleFrn.cpp
new file mode 100644
index 00000000..ed27c58b
--- /dev/null
+++ b/src/svxlink/modules/frn/ModuleFrn.cpp
@@ -0,0 +1,490 @@
+/**
+@file ModuleFrn.cpp
+@brief Free Radio Network (FRN) module
+@author sh123
+@date 2014-12-30
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004 Tobias Blomberg
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+#include
+#include "ModuleFrn.h"
+#include "multirate_filter_coeff.h"
+
+
+/****************************************************************************
+ *
+ * Namespaces to use
+ *
+ ****************************************************************************/
+using namespace std;
+using namespace Async;
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+/****************************************************************************
+ *
+ * Local class definitions
+ *
+ ****************************************************************************/
+
+
+/****************************************************************************
+ *
+ * Prototypes
+ *
+ ****************************************************************************/
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+/****************************************************************************
+ *
+ * Local Global Variables
+ *
+ ****************************************************************************/
+
+
+/****************************************************************************
+ *
+ * Pure C-functions
+ *
+ ****************************************************************************/
+extern "C" {
+ Module *module_init(void *dl_handle, Logic *logic, const char *cfg_name)
+ {
+ return new ModuleFrn(dl_handle, logic, cfg_name);
+ }
+} /* extern "C" */
+
+
+
+/****************************************************************************
+ *
+ * Public member functions
+ *
+ ****************************************************************************/
+ModuleFrn::ModuleFrn(void *dl_handle, Logic *logic, const string& cfg_name)
+ : Module(dl_handle, logic, cfg_name)
+ , qso(0)
+ , audio_valve(0)
+ , audio_splitter(0)
+ , audio_selector(0)
+{
+ cout << "\tModule Frn v" MODULE_FRN_VERSION " starting...\n";
+
+} /* ModuleFrn */
+
+
+ModuleFrn::~ModuleFrn(void)
+{
+ moduleCleanup();
+} /* ~ModuleFrn */
+
+
+/****************************************************************************
+ *
+ * Protected member functions
+ *
+ ****************************************************************************/
+
+
+/*
+ *------------------------------------------------------------------------
+ * Method:
+ * Purpose:
+ * Input:
+ * Output:
+ * Author:
+ * Created:
+ * Remarks:
+ * Bugs:
+ *------------------------------------------------------------------------
+ */
+
+
+/****************************************************************************
+ *
+ * Private member functions
+ *
+ ****************************************************************************/
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: initialize
+ * Purpose: Called by the core system right after the object has been
+ * constructed. As little of the initialization should be done in
+ * the constructor. It's easier to handle errors here.
+ * Input: None
+ * Output: Return \em true on success or else \em false should be returned
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2005-08-28
+ * Remarks: The base class initialize method must be called from here.
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+bool ModuleFrn::initialize(void)
+{
+ if (!Module::initialize())
+ {
+ return false;
+ }
+
+ qso = new QsoFrn(this);
+ qso->error.connect(
+ mem_fun(*this, &ModuleFrn::onQsoError));
+
+ // rig/mic -> frn
+ audio_valve = new AudioValve;
+ audio_splitter = new AudioSplitter;
+
+ AudioSink::setHandler(audio_valve);
+ audio_valve->registerSink(audio_splitter);
+#if INTERNAL_SAMPLE_RATE == 16000
+ AudioDecimator *down_sampler = new AudioDecimator(
+ 2, coeff_16_8, coeff_16_8_taps);
+ audio_splitter->addSink(down_sampler, true);
+ down_sampler->registerSink(qso);
+#else
+ audio_splitter->addSink(qso);
+#endif
+
+ // frn -> rig/speaker
+ audio_selector = new AudioSelector;
+ audio_fifo = new Async::AudioFifo(100 * 320 * 5);
+
+#if INTERNAL_SAMPLE_RATE == 16000
+ AudioInterpolator *up_sampler = new AudioInterpolator(
+ 2, coeff_16_8, coeff_16_8_taps);
+ qso->registerSink(up_sampler, true);
+ audio_selector->addSource(up_sampler);
+ audio_selector->enableAutoSelect(up_sampler, 0);
+#else
+ audio_selector->addSource(qso);
+ audio_selector->enableAutoSelect(qso, 0);
+#endif
+ audio_fifo->registerSource(audio_selector);
+ AudioSource::setHandler(audio_fifo);
+
+ if (!qso->initOk())
+ {
+ delete qso;
+ cerr << "*** ERROR: Creation of Qso object failed\n";
+ return false;
+ }
+
+ return true;
+
+} /* initialize */
+
+
+void ModuleFrn::moduleCleanup()
+{
+ AudioSource::clearHandler();
+ audio_fifo->unregisterSource();
+
+ audio_splitter->removeSink(qso);
+ audio_valve->unregisterSink();
+ AudioSink::clearHandler();
+
+ delete qso;
+ qso = 0;
+
+ delete audio_fifo;
+ audio_fifo = 0;
+
+ delete audio_splitter;
+ audio_splitter = 0;
+
+ delete audio_valve;
+ audio_valve = 0;
+
+ delete audio_selector;
+ audio_selector = 0;
+}
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: activateInit
+ * Purpose: Called by the core system when this module is activated.
+ * Input: None
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::activateInit(void)
+{
+ audio_valve->setOpen(true);
+ qso->connect();
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: deactivateCleanup
+ * Purpose: Called by the core system when this module is deactivated.
+ * Input: None
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2004-03-07
+ * Remarks: Do NOT call this function directly unless you really know what
+ * you are doing. Use Module::deactivate() instead.
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::deactivateCleanup(void)
+{
+ audio_valve->setOpen(true);
+ qso->disconnect();
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: dtmfDigitReceived
+ * Purpose: Called by the core system when a DTMF digit has been
+ * received. This function will only be called if the module
+ * is active.
+ * Input: digit - The DTMF digit received (0-9, A-D, *, #)
+ * duration - The length in milliseconds of the received digit
+ * Output: Return true if the digit is handled or false if not
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+bool ModuleFrn::dtmfDigitReceived(char digit, int duration)
+{
+ cout << "DTMF digit received in module " << name() << ": " << digit << endl;
+
+ return false;
+
+} /* dtmfDigitReceived */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: dtmfCmdReceived
+ * Purpose: Called by the core system when a DTMF command has been
+ * received. A DTMF command consists of a string of digits ended
+ * with a number sign (#). The number sign is not included in the
+ * command string. This function will only be called if the module
+ * is active.
+ * Input: cmd - The received command.
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::dtmfCmdReceived(const string& cmd)
+{
+ cout << "DTMF command received in module " << name() << ": " << cmd << endl;
+
+ if (cmd == "")
+ {
+ deactivateMe();
+ return;
+ }
+
+ stringstream ss;
+
+ switch (cmd[0])
+ {
+ case CMD_HELP:
+ playHelpMsg();
+ break;
+
+ case CMD_COUNT_CLIENTS:
+ {
+ if (!validateCommand(cmd, 1))
+ return;
+ ss << "count_clients ";
+ ss << qso->clientsCount();
+ break;
+ }
+
+ case CMD_RF_DISABLE:
+ {
+ if (!validateCommand(cmd, 2))
+ return;
+
+ bool disable = (cmd[1] != '0');
+ qso->setRfDisabled(disable);
+ cout << "rf disable: " << disable << endl;
+ ss << "rf_disable " << (qso->isRfDisabled() ? "1 " : "0 ")
+ << (disable ? "1" : "0");
+ break;
+ }
+
+ default:
+ ss << "unknown_command " << cmd;
+ break;
+ }
+
+ processEvent(ss.str());
+
+} /* dtmfCmdReceived */
+
+
+bool ModuleFrn::validateCommand(const string& cmd, size_t argc)
+{
+ if (cmd.size() == argc)
+ {
+ return true;
+ }
+ else
+ {
+ stringstream ss;
+ ss << "command_failed " << cmd;
+ processEvent(ss.str());
+ return false;
+ }
+} /* ModulrFrn::commandFailed */
+
+
+#if 0
+void ModuleFrn::dtmfCmdReceivedWhenIdle(const std::string &cmd)
+{
+
+} /* dtmfCmdReceivedWhenIdle */
+#endif
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: squelchOpen
+ * Purpose: Called by the core system when the squelch open or close.
+ * Input: is_open - Set to \em true if the squelch is open or \em false
+ * if it's not.
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2005-08-28
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::squelchOpen(bool is_open)
+{
+ qso->squelchOpen(is_open);
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: allMsgsWritten
+ * Purpose: Called by the core system when all announcement messages has
+ * been played. Note that this function also may be called even
+ * if it wasn't this module that initiated the message playing.
+ * Input: None
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2005-08-28
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::allMsgsWritten(void)
+{
+
+} /* allMsgsWritten */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method: reportState
+ * Purpose: This function is called by the logic core when it wishes the
+ * module to report its state on the radio channel. Typically this
+ * is done when a manual identification has been triggered by the
+ * user by sending a "*".
+ * This function will only be called if this module is active.
+ * Input: None
+ * Output: None
+ * Author: Tobias Blomberg / SM0SVX
+ * Created: 2005-08-28
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleFrn::reportState(void)
+{
+ stringstream ss;
+ ss << "count_clients " << qso->clientsCount();
+ processEvent(ss.str());
+} /* reportState */
+
+
+void ModuleFrn::onQsoError(void)
+{
+ cerr << "QSO errored, deactivating module" << endl;
+ deactivateMe();
+}
+
+/*
+ * This file has not been truncated
+ */
diff --git a/src/svxlink/modules/frn/ModuleFrn.h b/src/svxlink/modules/frn/ModuleFrn.h
new file mode 100644
index 00000000..99f1573d
--- /dev/null
+++ b/src/svxlink/modules/frn/ModuleFrn.h
@@ -0,0 +1,163 @@
+/**
+@file ModuleFrn.h
+@brief Free Radio Network (FRN) QSO module
+@author sh123
+@date 2014-12-30
+
+\verbatim
+A module (plugin) for the svxlink server, a multi purpose tranciever
+frontend system.
+Copyright (C) 2004-2005 Tobias Blomberg / SM0SVX
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+
+#ifndef MODULE_FRN_INCLUDED
+#define MODULE_FRN_INCLUDED
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+
+#include
+
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+
+#include
+#include
+
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+#include "QsoFrn.h"
+
+
+/****************************************************************************
+ *
+ * Forward declarations
+ *
+ ****************************************************************************/
+namespace Async
+{
+ class AudioSplitter;
+ class AudioValve;
+ class AudioSelector;
+ class AudioFifo;
+ class AudioJitterFifo;
+};
+
+
+/****************************************************************************
+ *
+ * Namespace
+ *
+ ****************************************************************************/
+
+//namespace MyNameSpace
+//{
+
+
+/****************************************************************************
+ *
+ * Forward declarations of classes inside of the declared namespace
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Class definitions
+ *
+ ****************************************************************************/
+
+/**
+@brief Free Radio Network (FRN) module
+@author sh123
+@date 2014-12-30
+*/
+class ModuleFrn : public Module
+{
+ public:
+ ModuleFrn(void *dl_handle, Logic *logic, const std::string& cfg_name);
+ ~ModuleFrn(void);
+ const char *compiledForVersion(void) const { return SVXLINK_VERSION; }
+
+ private:
+ void moduleCleanup();
+ bool initialize(void);
+ void activateInit(void);
+ void deactivateCleanup(void);
+ bool dtmfDigitReceived(char digit, int duration);
+ void dtmfCmdReceived(const std::string& cmd);
+ void squelchOpen(bool is_open);
+ void allMsgsWritten(void);
+ void reportState(void);
+ bool validateCommand(const std::string& cmd, size_t argc);
+ void onQsoError(void);
+
+ private:
+ QsoFrn *qso;
+ Async::AudioValve *audio_valve;
+ Async::AudioSplitter *audio_splitter;
+ Async::AudioSelector *audio_selector;
+ Async::AudioFifo *audio_fifo;
+
+ static const char CMD_HELP = '0';
+ static const char CMD_COUNT_CLIENTS = '1';
+ static const char CMD_RF_DISABLE = '2';
+
+}; /* class ModuleFrn */
+
+
+//} /* namespace */
+
+#endif /* MODULE_FRN_INCLUDED */
+
+
+/*
+ * This file has not been truncated
+ */
diff --git a/src/svxlink/modules/frn/QsoFrn.cpp b/src/svxlink/modules/frn/QsoFrn.cpp
new file mode 100644
index 00000000..7e1465dc
--- /dev/null
+++ b/src/svxlink/modules/frn/QsoFrn.cpp
@@ -0,0 +1,970 @@
+/**
+@file QsoFrn.cpp
+@brief Free Radio Network (FRN) QSO module
+@author sh123
+@date 2014-12-30
+
+This file contains a class that implementes the things needed for one
+EchoLink Qso.
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+#include "Utils.h"
+#include "ModuleFrn.h"
+#include "QsoFrn.h"
+#include "multirate_filter_coeff.h"
+
+
+/****************************************************************************
+ *
+ * Namespaces to use
+ *
+ ****************************************************************************/
+using namespace std;
+using namespace Async;
+using namespace sigc;
+using namespace FrnUtils;
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Local class definitions
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Prototypes
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Local Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Public member functions
+ *
+ ****************************************************************************/
+
+QsoFrn::QsoFrn(ModuleFrn *module)
+ : init_ok(false)
+ , tcp_client(new TcpClient(TCP_BUFFER_SIZE))
+ , rx_timeout_timer(new Timer(RX_TIMEOUT_TIME, Timer::TYPE_PERIODIC))
+ , con_timeout_timer(new Timer(CON_TIMEOUT_TIME, Timer::TYPE_PERIODIC))
+ , keepalive_timer(new Timer(KEEPALIVE_TIMEOUT_TIME, Timer::TYPE_PERIODIC))
+ , reconnect_timer(new Timer(KEEPALIVE_TIMEOUT_TIME, Timer::TYPE_ONESHOT))
+ , state(STATE_DISCONNECTED)
+ , connect_retry_cnt(0)
+ , send_buffer_cnt(0)
+ , gsmh(gsm_create())
+ , lines_to_read(-1)
+ , is_receiving_voice(false)
+ , is_rf_disabled(false)
+ , reconnect_timeout_ms(RECONNECT_TIMEOUT_TIME)
+ , opt_frn_debug(false)
+{
+ assert(module != 0);
+
+ Config &cfg = module->cfg();
+ const string &cfg_name = module->cfgName();
+
+ if (cfg.getValue(cfg_name, "FRN_DEBUG", opt_frn_debug))
+ cout << "frn debugging is enabled" << endl;
+
+ if (cfg.getValue(cfg_name, "DISABLE_RF", is_rf_disabled))
+ cout << "rf is disabled" << endl;
+
+ if (!cfg.getValue(cfg_name, "SERVER", opt_server))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/SERVER not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "PORT", opt_port))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/PORT not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "EMAIL_ADDRESS", opt_email_address))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/EMAIL_ADDRESS not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "DYN_PASSWORD", opt_dyn_password))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/DYN_PASSWORD not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "CALLSIGN_AND_USER", opt_callsign_and_user))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/CALLSIGN_AND_USER not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "CLIENT_TYPE", opt_client_type))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/CLIENT_TYPE not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "BAND_AND_CHANNEL", opt_band_and_channel))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/BAND_AND_CHANNEL not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "DESCRIPTION", opt_description))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/DESCRIPTION not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "COUNTRY", opt_country))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/COUNTRY not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "CITY_CITY_PART", opt_city_city_part))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/CITY_CITY_PART not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "NET", opt_net))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/NET not set\n";
+ return;
+ }
+ if (!cfg.getValue(cfg_name, "VERSION", opt_version))
+ {
+ cerr << "*** ERROR: Config variable " << cfg_name
+ << "/VERSION not set\n";
+ return;
+ }
+
+ int gsm_one = 1;
+ assert(gsm_option(gsmh, GSM_OPT_WAV49, &gsm_one) != -1);
+
+ tcp_client->connected.connect(
+ mem_fun(*this, &QsoFrn::onConnected));
+ tcp_client->disconnected.connect(
+ mem_fun(*this, &QsoFrn::onDisconnected));
+ tcp_client->dataReceived.connect(
+ mem_fun(*this, &QsoFrn::onDataReceived));
+ tcp_client->sendBufferFull.connect(
+ mem_fun(*this, &QsoFrn::onSendBufferFull));
+
+ this->rxVoiceStarted.connect(
+ mem_fun(*this, &QsoFrn::onRxVoiceStarted));
+ this->frnListReceived.connect(
+ mem_fun(*this, &QsoFrn::onFrnListReceived));
+ this->frnClientListReceived.connect(
+ mem_fun(*this, &QsoFrn::onFrnClientListReceived));
+
+ con_timeout_timer->setEnable(false);
+ con_timeout_timer->expired.connect(
+ mem_fun(*this, &QsoFrn::onConnectTimeout));
+
+ rx_timeout_timer->setEnable(false);
+ rx_timeout_timer->expired.connect(
+ mem_fun(*this, &QsoFrn::onRxTimeout));
+
+ keepalive_timer->setEnable(true);
+ keepalive_timer->expired.connect(
+ mem_fun(*this, &QsoFrn::onKeepaliveTimeout));
+
+ reconnect_timer->setEnable(false);
+ reconnect_timer->expired.connect(
+ mem_fun(*this, &QsoFrn::onDelayedReconnect));
+
+ init_ok = true;
+}
+
+
+QsoFrn::~QsoFrn(void)
+{
+ AudioSink::clearHandler();
+ AudioSource::clearHandler();
+
+ delete con_timeout_timer;
+ con_timeout_timer = 0;
+
+ delete rx_timeout_timer;
+ con_timeout_timer = 0;
+
+ delete tcp_client;
+ tcp_client = 0;
+
+ delete keepalive_timer;
+ keepalive_timer = 0;
+
+ gsm_destroy(gsmh);
+ gsmh = 0;
+}
+
+
+bool QsoFrn::initOk(void)
+{
+ return init_ok;
+}
+
+
+void QsoFrn::connect(void)
+{
+ setState(STATE_CONNECTING);
+
+ cout << "connecting to " << opt_server << ":" << opt_port << endl;
+ tcp_client->connect(opt_server, atoi(opt_port.c_str()));
+}
+
+
+void QsoFrn::disconnect(void)
+{
+ setState(STATE_DISCONNECTED);
+
+ con_timeout_timer->setEnable(false);
+
+ if (tcp_client->isConnected())
+ {
+ tcp_client->disconnect();
+ }
+}
+
+
+std::string QsoFrn::stateToString(State state)
+{
+ switch(state)
+ {
+ case STATE_DISCONNECTED:
+ return "DISCONNECTED";
+ case STATE_CONNECTING:
+ return "CONNECTING";
+ case STATE_CONNECTED:
+ return "CONNECTED";
+ case STATE_LOGGING_IN_1:
+ return "LOGGING_IN_1";
+ case STATE_LOGGING_IN_2:
+ return "LOGGIN_IN_2";
+ case STATE_IDLE:
+ return "IDLE";
+ case STATE_ERROR:
+ return "ERROR";
+ case STATE_TX_AUDIO_WAITING:
+ return "TX_AUDIO_WAITING";
+ case STATE_TX_AUDIO_APPROVED:
+ return "TX_AUDIO_APPROVED";
+ case STATE_TX_AUDIO:
+ return "TX_AUDIO";
+ case STATE_RX_AUDIO:
+ return "RX_AUDIO";
+ case STATE_RX_CLIENT_LIST_HEADER:
+ return "RX_CLIENT_LIST_HEADER";
+ case STATE_RX_CLIENT_LIST:
+ return "RX_CLIENT_LIST";
+ case STATE_RX_LIST:
+ return "RX_LIST";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+
+int QsoFrn::writeSamples(const float *samples, int count)
+{
+ //cout << __FUNCTION__ << " " << count << endl;
+ int samples_read = 0;
+ con_timeout_timer->reset();
+
+ while (samples_read < count)
+ {
+ int read_cnt = min(BUFFER_SIZE - send_buffer_cnt, count-samples_read);
+ for (int i = 0; i < read_cnt; i++)
+ {
+ float sample = samples[samples_read++];
+ if (sample > 1)
+ send_buffer[send_buffer_cnt++] = 32767;
+ else if (sample < -1)
+ send_buffer[send_buffer_cnt++] = -32767;
+ else
+ send_buffer[send_buffer_cnt++] = static_cast(32767.0 * sample);
+ }
+ if (send_buffer_cnt == BUFFER_SIZE)
+ {
+ if (state == STATE_TX_AUDIO)
+ {
+ sendVoiceData(send_buffer, send_buffer_cnt);
+ send_buffer_cnt = 0;
+ }
+ else
+ {
+ samples_read = count;
+ break;
+ }
+ }
+ }
+ return samples_read;
+}
+
+
+void QsoFrn::flushSamples(void)
+{
+ //cout << __FUNCTION__ << " " << stateToString(state) << endl;
+
+ if (state == STATE_TX_AUDIO)
+ {
+ if (send_buffer_cnt > 0)
+ {
+ memset(send_buffer + send_buffer_cnt, 0,
+ sizeof(send_buffer) - sizeof(*send_buffer) * send_buffer_cnt);
+ send_buffer_cnt = BUFFER_SIZE;
+
+ sendVoiceData(send_buffer, send_buffer_cnt);
+ send_buffer_cnt = 0;
+ }
+ sendRequest(RQ_TX0);
+ }
+ sourceAllSamplesFlushed();
+}
+
+
+void QsoFrn::resumeOutput(void)
+{
+ //cout << __FUNCTION__ << endl;
+}
+
+
+void QsoFrn::squelchOpen(bool is_open)
+{
+ if (is_open && state == STATE_IDLE)
+ {
+ sendRequest(RQ_TX0);
+ setState(STATE_TX_AUDIO_WAITING);
+ }
+}
+
+
+/****************************************************************************
+ *
+ * Protected member functions
+ *
+ ****************************************************************************/
+void QsoFrn::allSamplesFlushed(void)
+{
+ //cout << __FUNCTION__ << endl;
+}
+
+
+/****************************************************************************
+ *
+ * Private member functions
+ *
+ ****************************************************************************/
+void QsoFrn::setState(State newState)
+{
+ if (newState != state)
+ {
+ if (opt_frn_debug)
+ cout << "state: " << stateToString(newState) << endl;
+ state = newState;
+ stateChange(newState);
+ if (state == STATE_ERROR)
+ error();
+ }
+}
+
+
+void QsoFrn::login(void)
+{
+ assert(state == STATE_CONNECTED);
+
+ setState(STATE_LOGGING_IN_1);
+
+ std::stringstream s;
+ s << "CT:";
+ s << "" << opt_version << "";
+ s << "" << opt_email_address << "";
+ s << "" << opt_dyn_password << "";
+ s << "" << opt_callsign_and_user << "";
+ s << "" << opt_client_type << "";
+ s << "" << opt_band_and_channel << "";
+ s << "" << opt_description << "";
+ s << "" << opt_country << "";
+ s << "" << opt_city_city_part << "";
+ s << "" << opt_net << "";
+ s << endl;
+
+ std::string req = s.str();
+ tcp_client->write(req.c_str(), req.length());
+}
+
+void QsoFrn::sendVoiceData(short *data, int len)
+{
+ assert(len == BUFFER_SIZE);
+
+ size_t nbytes = 0;
+ unsigned char gsm_data[FRN_AUDIO_PACKET_SIZE];
+
+ for (int nframe = 0; nframe < FRAME_COUNT; nframe++)
+ {
+ short * src = data + nframe * PCM_FRAME_SIZE;
+ unsigned char * dst = gsm_data + nframe * GSM_FRAME_SIZE;
+
+ // GSM_OPT_WAV49, produce alternating frames 32, 33, 32, 33, ..
+ gsm_encode(gsmh, src, dst);
+ gsm_encode(gsmh, src + PCM_FRAME_SIZE / 2, dst + 32);
+
+ nbytes += GSM_FRAME_SIZE;
+ }
+ sendRequest(RQ_TX1);
+ size_t written = tcp_client->write(gsm_data, nbytes);
+ if (written != nbytes)
+ {
+ cerr << "not all voice data was written to FRN: "
+ << written << "\\" << nbytes << endl;
+ }
+}
+
+
+void QsoFrn::reconnect(void)
+{
+ reconnect_timeout_ms *= RECONNECT_BACKOFF;
+ if (connect_retry_cnt++ < MAX_CONNECT_RETRY_CNT)
+ {
+ cout << "reconnecting #" << connect_retry_cnt << endl;
+ connect();
+ }
+ else
+ {
+ cerr << "failed to reconnect " << MAX_CONNECT_RETRY_CNT << " times" << endl;
+ setState(STATE_ERROR);
+ }
+}
+
+
+void QsoFrn::sendRequest(Request rq)
+{
+ std::stringstream s;
+
+ switch(rq)
+ {
+ case RQ_RX0:
+ s << "RX0";
+ break;
+
+ case RQ_TX0:
+ s << "TX0";
+ break;
+
+ case RQ_TX1:
+ s << "TX1";
+ break;
+
+ case RQ_P:
+ s << "P";
+ break;
+
+ default:
+ cerr << "unknown request " << rq << endl;
+ return;
+ }
+ if (opt_frn_debug)
+ cout << "req: " << s.str() << endl;
+ if (tcp_client->isConnected())
+ {
+ s << "\r\n";
+ std::string rq_s = s.str();
+ size_t written = tcp_client->write(rq_s.c_str(), rq_s.length());
+ if (written != rq_s.length())
+ {
+ cerr << "request " << rq_s << " was not written to FRN: "
+ << written << "\\" << rq_s.length() << endl;
+ }
+ }
+}
+
+
+int QsoFrn::handleAudioData(unsigned char *data, int len)
+{
+ unsigned char *gsm_data = data + CLIENT_INDEX_SIZE;
+ short *pcm_buffer = receive_buffer;
+ float pcm_samples[PCM_FRAME_SIZE];
+
+ if (len < FRN_AUDIO_PACKET_SIZE + CLIENT_INDEX_SIZE)
+ return 0;
+
+ if (!is_receiving_voice)
+ {
+ unsigned short client_index = data[1] | data[0] << 8;
+ is_receiving_voice = true;
+ if (client_index > 0 && client_index <= client_list.size())
+ rxVoiceStarted(client_list[client_index - 1]);
+ }
+
+ if (!is_rf_disabled)
+ {
+ for (int frameno = 0; frameno < FRAME_COUNT; frameno++)
+ {
+ unsigned char *src = gsm_data + frameno * GSM_FRAME_SIZE;
+ short *dst = pcm_buffer;
+ bool is_gsm_decode_success = true;
+
+ // GSM_OPT_WAV49, consume alternating frames of size 33, 32, 33, 32, ..
+ if (gsm_decode(gsmh, src, dst) == -1)
+ is_gsm_decode_success = false;
+
+ if (gsm_decode(gsmh, src + 33, dst + PCM_FRAME_SIZE / 2) == -1)
+ is_gsm_decode_success = false;
+
+ if (!is_gsm_decode_success)
+ cerr << "gsm decoder failed to decode frame " << frameno << endl;
+
+ for (int i = 0; i < PCM_FRAME_SIZE; i++)
+ pcm_samples[i] = static_cast(pcm_buffer[i]) / 32768.0;
+
+ int all_written = 0;
+ while (all_written < PCM_FRAME_SIZE)
+ {
+ int written = sinkWriteSamples(pcm_samples + all_written,
+ PCM_FRAME_SIZE - all_written);
+ if (written == 0)
+ {
+ cerr << "cannot write frame to sink, dropping sample "
+ << (PCM_FRAME_SIZE - all_written) << endl;
+ break;
+ }
+ all_written += written;
+ }
+ pcm_buffer += PCM_FRAME_SIZE;
+ }
+ }
+ setState(STATE_IDLE);
+ rx_timeout_timer->setEnable(true);
+ rx_timeout_timer->reset();
+ sendRequest(RQ_P);
+ return FRN_AUDIO_PACKET_SIZE + CLIENT_INDEX_SIZE;
+}
+
+
+int QsoFrn::handleCommand(unsigned char *data, int len)
+{
+ int bytes_read = 0;
+ Response cmd = (Response)data[0];
+ if (opt_frn_debug)
+ cout << "cmd: " << cmd << endl;
+
+ keepalive_timer->reset();
+
+ bytes_read += 1;
+
+ switch (cmd)
+ {
+ case DT_IDLE:
+ sendRequest(RQ_P);
+ setState(STATE_IDLE);
+ break;
+
+ case DT_DO_TX:
+ setState(STATE_TX_AUDIO_APPROVED);
+ break;
+
+ case DT_VOICE_BUFFER:
+ setState(STATE_RX_AUDIO);
+ rx_timeout_timer->setEnable(true);
+ rx_timeout_timer->reset();
+ break;
+
+ case DT_TEXT_MESSAGE:
+ case DT_NET_NAMES:
+ case DT_ADMIN_LIST:
+ case DT_ACCESS_LIST:
+ case DT_BLOCK_LIST:
+ case DT_MUTE_LIST:
+ case DT_ACCESS_MODE:
+ setState(STATE_RX_LIST);
+ break;
+
+ case DT_CLIENT_LIST:
+ setState(STATE_RX_CLIENT_LIST_HEADER);
+ break;
+
+ default:
+ cout << "unknown command " << cmd << endl;
+ break;
+ }
+ return bytes_read;
+}
+
+
+int QsoFrn::handleListHeader(unsigned char *data, int len)
+{
+ int bytes_read = 0;
+
+ if (len >= CLIENT_INDEX_SIZE)
+ {
+ bytes_read += CLIENT_INDEX_SIZE;
+ setState(STATE_RX_CLIENT_LIST);
+ lines_to_read = -1;
+ }
+ return bytes_read;
+}
+
+
+int QsoFrn::handleList(unsigned char *data, int len)
+{
+ int bytes_read = 0;
+ std::string line;
+ std::istringstream lines(std::string((char*)data, len));
+ bool has_win_newline = hasWinNewline(lines);
+
+ if (hasLine(lines) && safeGetline(lines, line))
+ {
+ if (lines_to_read == -1)
+ {
+ lines_to_read = atoi(line.c_str());
+ }
+ else
+ {
+ cur_item_list.push_back(line);
+ lines_to_read--;
+ }
+ bytes_read += line.length() + (has_win_newline ? 2 : 1);
+ }
+ if (lines_to_read == 0)
+ {
+ if (state == STATE_RX_CLIENT_LIST)
+ frnClientListReceived(cur_item_list);
+ frnListReceived(cur_item_list);
+ cur_item_list.clear();
+ lines_to_read = -1;
+ setState(STATE_IDLE);
+ }
+ //cout << "got " << len << " read " << bytes_read << endl;
+ return bytes_read;
+}
+
+
+int QsoFrn::handleLogin(unsigned char *data, int len, bool stage_one)
+{
+ int bytes_read = 0;
+ std::string line;
+ std::istringstream lines(std::string((char*)data, len));
+ bool has_win_newline = hasWinNewline(lines);
+
+ if (hasLine(lines) && safeGetline(lines, line))
+ {
+ if (stage_one)
+ {
+ if (line.length() == std::string("2014003").length() ||
+ line.length() == std::string("0").length())
+ {
+ setState(STATE_LOGGING_IN_2);
+ cout << "login stage 1 completed: " << line << endl;
+ }
+ else
+ {
+ setState(STATE_ERROR);
+ cerr << "login stage 1 failed: " << line << endl;
+ }
+ }
+ else
+ {
+ if (line.find("BLOCK") == std::string::npos &&
+ line.find("WRONG") == std::string::npos)
+ {
+ setState(STATE_IDLE);
+ sendRequest(RQ_RX0);
+ cout << "login stage 2 completed: " << line << endl;
+ }
+ else
+ {
+ setState(STATE_ERROR);
+ cerr << "login stage 2 failed: " << line << endl;
+ }
+ }
+ bytes_read += line.length() + (has_win_newline ? 2 : 1);
+ }
+ return bytes_read;
+}
+
+
+void QsoFrn::onConnected(void)
+{
+ //cout << __FUNCTION__ << endl;
+ setState(STATE_CONNECTED);
+
+ connect_retry_cnt = 0;
+ reconnect_timeout_ms = RECONNECT_TIMEOUT_TIME;
+ con_timeout_timer->setEnable(true);
+ login();
+}
+
+
+void QsoFrn::onDisconnected(TcpConnection *conn,
+ TcpConnection::DisconnectReason reason)
+{
+ //cout << __FUNCTION__ << " ";
+ bool needs_reconnect = false;
+
+ setState(STATE_DISCONNECTED);
+
+ con_timeout_timer->setEnable(false);
+
+ switch (reason)
+ {
+ case TcpConnection::DR_HOST_NOT_FOUND:
+ cout << "DR_HOST_NOT_FOUND" << endl;
+ needs_reconnect = true;
+ break;
+
+ case TcpConnection::DR_REMOTE_DISCONNECTED:
+ cout << "DR_REMOTE_DISCONNECTED" << ", "
+ << conn->disconnectReasonStr(reason) << endl;
+ needs_reconnect = true;
+ break;
+
+ case TcpConnection::DR_SYSTEM_ERROR:
+ cout << "DR_SYSTEM_ERROR" << ", "
+ << conn->disconnectReasonStr(reason) << endl;
+ needs_reconnect = true;
+ break;
+
+ case TcpConnection::DR_RECV_BUFFER_OVERFLOW:
+ cout << "DR_RECV_BUFFER_OVERFLOW" << endl;
+ setState(STATE_ERROR);
+ break;
+
+ case TcpConnection::DR_ORDERED_DISCONNECT:
+ cout << "DR_ORDERED_DISCONNECT" << endl;
+ break;
+
+ default:
+ cout << "DR_UNKNOWN" << endl;
+ setState(STATE_ERROR);
+ break;
+ }
+
+ if (needs_reconnect)
+ {
+ cout << "reconnecting in " << reconnect_timeout_ms << " ms" << endl;
+ reconnect_timer->setEnable(true);
+ reconnect_timer->setTimeout(reconnect_timeout_ms);
+ reconnect_timer->reset();
+ }
+}
+
+
+int QsoFrn::onDataReceived(TcpConnection *con, void *data, int len)
+{
+ //cout << __FUNCTION__ << " len: " << len << endl;
+ unsigned char *p_data = (unsigned char*)data;
+
+ con_timeout_timer->reset();
+ int remaining_bytes = len;
+
+ while (remaining_bytes > 0)
+ {
+ int bytes_read = 0;
+
+ switch (state)
+ {
+ case STATE_LOGGING_IN_1:
+ bytes_read += handleLogin(p_data, remaining_bytes, true);
+ break;
+
+ case STATE_LOGGING_IN_2:
+ bytes_read += handleLogin(p_data, remaining_bytes, false);
+ break;
+
+ case STATE_TX_AUDIO_APPROVED:
+ if (remaining_bytes >= CLIENT_INDEX_SIZE)
+ bytes_read += CLIENT_INDEX_SIZE;
+ setState(STATE_TX_AUDIO);
+ break;
+
+ case STATE_TX_AUDIO:
+ case STATE_TX_AUDIO_WAITING:
+ case STATE_IDLE:
+ bytes_read += handleCommand(p_data, remaining_bytes);
+ break;
+
+ case STATE_RX_AUDIO:
+ bytes_read += handleAudioData(p_data, remaining_bytes);
+ break;
+
+ case STATE_RX_CLIENT_LIST_HEADER:
+ bytes_read += handleListHeader(p_data, remaining_bytes);
+ break;
+
+ case STATE_RX_LIST:
+ case STATE_RX_CLIENT_LIST:
+ bytes_read += handleList(p_data, remaining_bytes);
+ break;
+
+ default:
+ break;
+ }
+ //cout << bytes_read << " " << remaining_bytes << " " << len << endl;
+ if (bytes_read == 0)
+ break;
+ remaining_bytes -= bytes_read;
+ p_data += bytes_read;
+ }
+ return len - remaining_bytes;
+}
+
+
+void QsoFrn::onSendBufferFull(bool is_full)
+{
+ cerr << "send buffer is full " << is_full << endl;
+}
+
+
+void QsoFrn::onConnectTimeout(Timer *timer)
+{
+ //cout << __FUNCTION__ << endl;
+ if (state == STATE_IDLE)
+ {
+ disconnect();
+ reconnect();
+ }
+}
+
+
+void QsoFrn::onRxTimeout(Timer *timer)
+{
+ //cout << __FUNCTION__ << endl;
+ sinkFlushSamples();
+ rx_timeout_timer->setEnable(false);
+ is_receiving_voice = false;
+ //setState(STATE_IDLE);
+ sendRequest(RQ_P);
+}
+
+
+void QsoFrn::onKeepaliveTimeout(Timer *timer)
+{
+ if (state == STATE_IDLE)
+ sendRequest(RQ_P);
+}
+
+
+void QsoFrn::onRxVoiceStarted(const string &client_descritpion) const
+{
+ if (is_rf_disabled)
+ cout << "[listen only] ";
+ cout << "voice started: " << client_descritpion << endl;
+}
+
+
+void QsoFrn::onFrnListReceived(const FrnList &list) const
+{
+ cout << "FRN list received:" << endl;
+ for (FrnList::const_iterator it = list.begin(); it != list.end(); ++it)
+ {
+ cout << "-- " << *it << endl;
+ }
+}
+
+
+void QsoFrn::onFrnClientListReceived(const FrnList &list)
+{
+ cout << "FRN active client list updated" << endl;
+ client_list = list;
+}
+
+
+void QsoFrn::onDelayedReconnect(Async::Timer *timer)
+{
+ reconnect();
+}
+
+/*
+ * This file has not been truncated
+ */
+
diff --git a/src/svxlink/modules/frn/QsoFrn.h b/src/svxlink/modules/frn/QsoFrn.h
new file mode 100644
index 00000000..86569406
--- /dev/null
+++ b/src/svxlink/modules/frn/QsoFrn.h
@@ -0,0 +1,584 @@
+/**
+@file QsoFrn.h
+@brief Free Radio Network (FRN) QSO module
+@author sh123
+@date 2014-12-30
+
+This file contains a class that implementes the things needed for one
+Frn Qso.
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+#ifndef QSO_FRN_INCLUDED
+#define QSO_FRN_INCLUDED
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+extern "C" {
+#include
+}
+
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Forward declarations
+ *
+ ****************************************************************************/
+namespace Async
+{
+ class Config;
+ class AudioPacer;
+ class AudioFifo;
+ class AudioPassthrough;
+ class TcpClient;
+ class TcpConnection;
+};
+
+
+/****************************************************************************
+ *
+ * Namespace
+ *
+ ****************************************************************************/
+
+//namespace MyNameSpace
+//{
+
+
+/****************************************************************************
+ *
+ * Forward declarations of classes inside of the declared namespace
+ *
+ ****************************************************************************/
+class MsgHandler;
+class EventHandler;
+class AsyncTimer;
+class AudioFifo;
+class ModuleFrn;
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+typedef std::vector FrnList;
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Class definitions
+ *
+ ****************************************************************************/
+
+/**
+@brief Free Radio Network (FRN) QSO module
+@author sh123
+@date 2014-12-30
+
+A class that implementes the things needed for FRN QSO.
+*/
+class QsoFrn
+ : public Async::AudioSink, public Async::AudioSource, public sigc::trackable
+{
+ public:
+ typedef enum {
+ STATE_ERROR,
+ STATE_DISCONNECTED,
+ STATE_CONNECTING,
+ STATE_CONNECTED,
+ STATE_LOGGING_IN_1,
+ STATE_LOGGING_IN_2,
+ STATE_IDLE,
+ STATE_TX_AUDIO_WAITING,
+ STATE_TX_AUDIO_APPROVED,
+ STATE_TX_AUDIO,
+ STATE_RX_AUDIO,
+ STATE_RX_CLIENT_LIST_HEADER,
+ STATE_RX_CLIENT_LIST,
+ STATE_RX_LIST
+ } State;
+
+ typedef enum {
+ DT_IDLE = 0,
+ DT_DO_TX,
+ DT_VOICE_BUFFER,
+ DT_CLIENT_LIST,
+ DT_TEXT_MESSAGE,
+ DT_NET_NAMES,
+ DT_ADMIN_LIST,
+ DT_ACCESS_LIST,
+ DT_BLOCK_LIST,
+ DT_MUTE_LIST,
+ DT_ACCESS_MODE
+ } Response;
+
+ typedef enum {
+ RQ_RX0,
+ RQ_TX0,
+ RQ_TX1,
+ RQ_P
+ } Request;
+
+ /**
+ * @brief Default constuctor
+ */
+ QsoFrn(ModuleFrn *module);
+
+ /**
+ * @brief Destructor
+ */
+ virtual ~QsoFrn(void);
+
+ /**
+ * @brief Call to check if QSO was ctor-ed succesfully
+ *
+ * @return true if QSO was constructed correctly
+ *
+ * Call after qso contruction to make sure that it was ctor-ed correctly.
+ */
+ bool initOk(void);
+
+ /**
+ * @brief Method to put QSO to live
+ */
+ void connect(void);
+
+ /**
+ * @brief Disconnect QSO from server, put it to disconnected state
+ */
+ void disconnect(void);
+
+ /**
+ * @brief Called by FRN module to notify that squelch is opened
+ *
+ * @param true if squelch is open or false if closed
+ */
+ void squelchOpen(bool is_open);
+
+ /**
+ * @brief Write samples into this audio sink
+ * @param samples The buffer containing the samples
+ * @param count The number of samples in the buffer
+ * @return Returns the number of samples that has been taken care of
+ *
+ * This function is used to write audio into this audio sink. If it
+ * returns 0, no more samples could be written.
+ * If the returned number of written samples is lower than the count
+ * parameter value, the sink is not ready to accept more samples.
+ * In this case, the audio source requires sample buffering to temporarily
+ * store samples that are not immediately accepted by the sink.
+ * The writeSamples function should be called on source buffer updates
+ * and after a source output request has been received through the
+ * requestSamples function.
+ * This function is normally only called from a connected source object.
+ */
+ virtual int writeSamples(const float *samples, int count);
+
+ /**
+ * @brief Tell the sink to flush the previously written samples
+ *
+ * This function is used to tell the sink to flush previously written
+ * samples. When done flushing, the sink should call the
+ * sourceAllSamplesFlushed function.
+ * This function is normally only called from a connected source object.
+ */
+ virtual void flushSamples(void);
+
+ /**
+ * @brief Return true if qso is connected to the server
+ *
+ * @return true if qso is connected
+ */
+ bool isConnected() const { return state >= STATE_IDLE; }
+
+ /**
+ * @brief return number of clients on the channel
+ *
+ * @return count of clients on the channel including myself
+ */
+ int clientsCount() const { return client_list.size(); }
+
+ /**
+ * @brief Resume audio output to the sink
+ *
+ * This function will be called when the registered audio sink is ready
+ * to accept more samples.
+ * This function is normally only called from a connected sink object.
+ */
+ virtual void resumeOutput(void);
+
+ /**
+ * @brief Do not send anything to rig
+ *
+ * @param true to disable audio stream to rig
+ */
+ void setRfDisabled(bool disabled) { is_rf_disabled = disabled; }
+
+ /**
+ * @brief Return true if rf tx disabled for the qso
+ *
+ * @return true if rf tx is disabled
+ */
+ bool isRfDisabled() const { return is_rf_disabled; }
+
+ /**
+ * @brief QSO is erroring out and cannot recover itself
+ */
+ sigc::signal error;
+
+ /**
+ * @brief QSO internal state has been changed
+ * @param QSO state enum value
+ */
+ sigc::signal stateChange;
+
+ protected:
+ /**
+ * @brief The registered sink has flushed all samples
+ *
+ * This function will be called when all samples have been flushed in the
+ *
+ * registered sink. If it is not reimplemented, a handler must be set
+ * that handle the function call.
+ * This function is normally only called from a connected sink object.
+ */
+ virtual void allSamplesFlushed(void);
+
+ /**
+ * @brief Converts QSO state enum to string for debugging
+ * @param QSO state enum
+ * @return String representation of the enum value
+ *
+ * This function is used to convert QSO state enum to readable
+ * string for debugging purposes
+ */
+ std::string stateToString(State state);
+
+ /**
+ * @brief Called internally to set new QSO state
+ *
+ * @param New state to set
+ */
+ void setState(State newState);
+
+ /**
+ * @brief Emitted when FRN list is fully populated
+ *
+ * @param vector of strings populated from FRN
+ */
+ sigc::signal frnListReceived;
+
+ /**
+ * @brief Emitted when FRN client list is fully populated
+ *
+ * @param vector of strings populated from FRN
+ */
+ sigc::signal frnClientListReceived;
+
+ /**
+ * @brief Emitted when started receiving voice from client
+ *
+ * @param string with client xml data
+ */
+ sigc::signal rxVoiceStarted;
+
+ /**
+ * @brief Called when started receiving voice from the client
+ *
+ * @param xml FRN format client descrition
+ */
+ void onRxVoiceStarted(const std::string &client_description) const;
+
+ /**
+ * @brief Called when completed FRN list is received
+ *
+ * @param xml FRN format client descrition line
+ */
+ void onFrnListReceived(const FrnList &list) const;
+
+ /**
+ * @brief Called when completed FRN client list is received
+ *
+ * @param xml FRN format client descrition line
+ */
+ void onFrnClientListReceived(const FrnList &list);
+
+ private:
+
+ /**
+ * @brief Initiate connection to FRN server when connection was lost
+ *
+ * Starts new connection retry. If maximum retry count is reached sets
+ * QSO state to error.
+ */
+ void reconnect();
+
+ /**
+ * @brief Starts initial FRN server login process
+ *
+ * Sends connection string with credentials and other required information
+ * to the FRN server, sets proper QSO state.
+ */
+ void login(void);
+
+ /**
+ * @brief Sends pcm raw frames to FRN server
+ *
+ * @param Pcm buffer with samples
+ * @param Size of buffer
+ */
+ void sendVoiceData(short *data, int len);
+
+ /**
+ * @brief Sends FRN client request to the server
+ *
+ * @param Request to send out
+ *
+ * This method is used to send FRN control request.
+ */
+ void sendRequest(Request rq);
+
+ /**
+ * @brief Consumed and handles incoming data when in loging in stage
+ *
+ * @param pointer to received data
+ * @param length of incoming data
+ * @param true if logging in stage 1, else stage 2
+ * @return count of bytes consumed
+ *
+ * Handles incoming data when in logging in stage 1 or 2,
+ * switches to next state if all required data was consumed.
+ */
+ int handleLogin(unsigned char *data, int len, bool stage_one);
+
+ /**
+ * @brief Consume and handle one incoming FRN command
+ * @param pointer to received data
+ * @param length of received data
+ * @return count of bytes consumed
+ *
+ * Consumes and handles one incoming FRN command and sets
+ * internal QSO state accordingly.
+ */
+ int handleCommand(unsigned char *data, int len);
+
+ /**
+ * @brief Consume and handle one incoming audio frame
+ *
+ * @param pointer to received data
+ * @param length of received data
+ * @return count of bytes consumed
+ *
+ * Consumes and handles one incoming FRN audio frame. Decodes
+ * from gsm wav49 format and sends to audio sink.
+ */
+ int handleAudioData(unsigned char *data, int len);
+
+ /**
+ * @brief Consume and handle FRN incoming string lists
+ *
+ * @param pointer to received data
+ * @param length of received data
+ * @return count of bytes consumed
+ *
+ * Method is used to consume and handle FRN incoming list
+ * header
+ */
+ int handleListHeader(unsigned char *data, int len);
+
+ /**
+ * @brief Consume and handle FRN incoming string lists
+ *
+ * @param pointer to received data
+ * @param length of received data
+ * @return count of bytes consumed
+ *
+ * Method is used to consume and handle FRN incoming lists, for
+ * example list of users, chat messages, etc.
+ */
+ int handleList(unsigned char *data, int len);
+
+ /**
+ * @brief Called when connection to FRN server is established
+ *
+ * Called when connection to FRN server is established, updates
+ * internal state, starts timeout timer.
+ */
+ void onConnected(void);
+
+ /**
+ * @brief Called when connection to FRN server is lost
+ *
+ * Called when connection to FRN server is lost, based on
+ * disconnect reason sets the internal QSO state and start
+ * reconnection if needed.
+ */
+ void onDisconnected(Async::TcpConnection *conn,
+ Async::TcpConnection::DisconnectReason reason);
+
+ /**
+ * @brief Called when new data is received from FRN server
+ *
+ * Called when new data is received from FRN server, calls proper
+ * handler based on the current state.
+ */
+ int onDataReceived(Async::TcpConnection *con, void *data, int len);
+
+ /**
+ * @brief Called when FRN connection buffer is full
+ */
+ void onSendBufferFull(bool is_full);
+
+ /**
+ * @brief Called when no pings were received during timeout time
+ *
+ * Initiates reconnection when no pings/data was received from
+ * the FRN serer during timeout time.
+ */
+ void onConnectTimeout(Async::Timer *timer);
+
+ /**
+ * @brief Called when no voice data was received during timeout time
+ *
+ * Called when no more voice data is received during timeout time,
+ * which is treated as end of voice data receive state.
+ */
+ void onRxTimeout(Async::Timer *timer);
+
+ /**
+ * @brief Called when wait for tx timer timeouts
+ *
+ * Called when server does not respond withing timeout time
+ * to TX request
+ */
+ void onTxWaitTimeout(Async::Timer *timer);
+
+ /**
+ * @brief Called when no pings/data were received during timeout time
+ *
+ * Called when no pings and data were received during timeout time
+ */
+ void onKeepaliveTimeout(Async::Timer *timer);
+
+ /**
+ * @brief Delayed reconnect timer callback
+ *
+ * Called to start delayed reconnection to FRN server
+ */
+ void onDelayedReconnect(Async::Timer *timer);
+
+ private:
+ static const int CLIENT_INDEX_SIZE = 2;
+ static const int TCP_BUFFER_SIZE = 65536;
+ static const int FRAME_COUNT = 5;
+ static const int PCM_FRAME_SIZE = 160*2; // WAV49 has 2x
+ static const int GSM_FRAME_SIZE = 65; // WAV49 has 65
+ static const int BUFFER_SIZE = FRAME_COUNT*PCM_FRAME_SIZE;
+ static const int FRN_AUDIO_PACKET_SIZE = FRAME_COUNT*GSM_FRAME_SIZE;
+
+ static const int CON_TIMEOUT_TIME = 30000;
+ static const int RX_TIMEOUT_TIME = 1000;
+ static const int KEEPALIVE_TIMEOUT_TIME = 5000;
+
+ static const int MAX_CONNECT_RETRY_CNT = 5;
+ static const int RECONNECT_TIMEOUT_TIME = 2000;
+ static const int RECONNECT_BACKOFF = 5;
+
+ bool init_ok;
+
+ Async::TcpClient * tcp_client;
+ Async::Timer * rx_timeout_timer;
+ Async::Timer * con_timeout_timer;
+ Async::Timer * keepalive_timer;
+ Async::Timer * reconnect_timer;
+ State state;
+ int connect_retry_cnt;
+ short receive_buffer[BUFFER_SIZE];
+ short send_buffer[BUFFER_SIZE];
+ int send_buffer_cnt;
+ gsm gsmh;
+ int lines_to_read;
+ FrnList cur_item_list;
+ FrnList client_list;
+ bool is_receiving_voice;
+ bool is_rf_disabled;
+ int reconnect_timeout_ms;
+
+ bool opt_frn_debug;
+ std::string opt_server;
+ std::string opt_port;
+ std::string opt_version;
+ std::string opt_email_address;
+ std::string opt_dyn_password;
+ std::string opt_callsign_and_user;
+ std::string opt_client_type;
+ std::string opt_band_and_channel;
+ std::string opt_description;
+ std::string opt_country;
+ std::string opt_city_city_part;
+ std::string opt_net;
+};
+
+
+//} /* namespace */
+
+#endif /* QSO_FRN_INCLUDED */
+
+
+/*
+ * This file has not been truncated
+ */
+
diff --git a/src/svxlink/modules/frn/Utils.cpp b/src/svxlink/modules/frn/Utils.cpp
new file mode 100644
index 00000000..2fe0ae6f
--- /dev/null
+++ b/src/svxlink/modules/frn/Utils.cpp
@@ -0,0 +1,89 @@
+/**
+@file Utils.cpp
+@brief Helpers for qso frn module
+@author sh123
+@date 2014-12-30
+
+This file contains a class that implementes the things needed for one
+EchoLink Qso.
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+#include
+#include
+#include
+#include
+
+
+namespace FrnUtils
+{
+
+std::istream& safeGetline(std::istream& is, std::string& t)
+{
+ t.clear();
+ std::istream::sentry se(is, true);
+ std::streambuf* sb = is.rdbuf();
+
+ for(;;) {
+ int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ if(sb->sgetc() == '\r')
+ sb->sbumpc();
+ return is;
+ case '\r':
+ if(sb->sgetc() == '\n')
+ {
+ sb->sbumpc();
+ return is;
+ }
+ t += (char)c;
+ break;
+ case EOF:
+ if(t.empty())
+ is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += (char)c;
+ break;
+ }
+ }
+}
+
+
+bool hasLine(std::istringstream& is)
+{
+ return (is.str().find('\n') != std::string::npos);
+}
+
+
+bool hasWinNewline(std::istringstream& is)
+{
+ return !(is.str().find("\r\n") == std::string::npos &&
+ is.str().find("\n\r") == std::string::npos);
+}
+
+
+} // namspace FrnUtils
+
+/*
+ * This file has not been truncated
+ */
+
diff --git a/src/svxlink/modules/frn/Utils.h b/src/svxlink/modules/frn/Utils.h
new file mode 100644
index 00000000..bae998cc
--- /dev/null
+++ b/src/svxlink/modules/frn/Utils.h
@@ -0,0 +1,58 @@
+/**
+@file Utils.h
+@brief Helpers for QSO FRN module
+@author sh123
+@date 2014-12-30
+
+This file contains a class that implementes the things needed for one
+Frn Qso.
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+namespace FrnUtils
+{
+
+/**
+ * @brief Checks if there is a line in string stream
+ *
+ * This functions checks if string stream has a line, returns true
+ * when line has been found.
+ */
+bool hasLine(std::istringstream& is);
+
+/**
+ * @brief Checks if string stream has windows style newline
+ *
+ * This function returns true if windows style newline has been found
+ * in given stream.
+ */
+bool hasWinNewline(std::istringstream& is);
+
+/**
+ * @brief Returns line from string stream halding also windows newlines
+ *
+ * This function is similar to std::getline, but takes care of windows
+ * style new lines.
+ */
+std::istream& safeGetline(std::istream& is, std::string& t);
+
+} // namespace FrnUtils
+
diff --git a/src/svxlink/modules/frn/frn-register b/src/svxlink/modules/frn/frn-register
new file mode 100755
index 00000000..d4a00c0b
--- /dev/null
+++ b/src/svxlink/modules/frn/frn-register
@@ -0,0 +1,99 @@
+#!/bin/bash -e
+
+SYSMAN_SERVER=sysman.freeradionetwork.eu
+#SYSMAN_SERVER=localhost
+REG_PORT=10025
+
+shopt -s extglob
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+CFG_FILE="$1"
+
+# Read the FRN module config file
+if [[ ! -r "$CFG_FILE" ]]; then
+ echo "*** ERROR: Could not read the FRN configuration file: $CFG_FILE"
+ exit 1
+fi
+source <(grep -v "^\[" < $CFG_FILE)
+
+# Check syntax of configuration variables
+if ! [[ "$CALLSIGN_AND_USER" =~ ^.+,\ .+$ ]]; then
+ echo "*** ERROR: CALLSIGN_AND_USER not set or malformed." \
+ "Should be \"Callsign, Name\""
+ exit 1
+fi
+if ! [[ "$EMAIL_ADDRESS" =~ ^.+@.+$ ]]; then
+ echo "*** ERROR: EMAIL_ADDRESS not set or malformed." \
+ "Should be \"user@example.org\""
+ exit 1
+fi
+if ! [[ "$BAND_AND_CHANNEL" =~ ^PC\ Only$ || \
+ "$BAND_AND_CHANNEL" =~ ^Crosslink$ ||
+ "$BAND_AND_CHANNEL" =~ ^[0-9]+\.[0-9]+(AM|FM|DIG)(\ (CTC[0-9.]+|DSC[0-9]+|NONE))?$ ]]; then
+ echo "*** ERROR: BAND_AND_CHANNEL not set or malformed." \
+ "Should be \"PC Only\", \"Crosslink\" or ..."
+ exit 1
+fi
+if [[ -z "$COUNTRY" ]]; then
+ echo "*** ERROR: COUNTRY not set."
+ exit 1
+fi
+if ! [[ "$CITY_CITY_PART" =~ ^.+\ -\ .+$ ]]; then
+ echo "*** ERROR: CITY_CITY_PART not set or malformed." \
+ "Should be \"city - city_part\""
+ exit 1
+fi
+
+echo "--- About to register using the following information ---"
+echo "Callsign and user: ${CALLSIGN_AND_USER}"
+echo "E-mail address: ${EMAIL_ADDRESS}"
+echo "Band and channel: ${BAND_AND_CHANNEL}"
+echo "Description: ${DESCRIPTION}"
+echo "Country: ${COUNTRY}"
+echo "City and city part: ${CITY_CITY_PART}"
+echo
+
+ANS=""
+until [[ "$ANS" =~ ^[yYnN] ]]; do
+ read -p "OK [Y/N]? " ANS
+done
+
+if [[ "$ANS" =~ ^[yY] ]]; then
+ REG_CMD="IG:${CALLSIGN_AND_USER}${EMAIL_ADDRESS}${BAND_AND_CHANNEL}${DESCRIPTION}${COUNTRY}${CITY_CITY_PART}"
+
+ echo "Registering..."
+ if ! exec 100<>/dev/tcp/${SYSMAN_SERVER}/${REG_PORT}; then
+ echo "*** ERROR: Could not connect to system manager"
+ exit 1
+ fi
+ echo "$REG_CMD" >&100
+ if ! read -u 100 -t 30 ANS; then
+ echo "*** ERROR: Could not read answer from system manager"
+ exit 1
+ fi
+ # Remove trailing whitespace
+ ANS=${ANS%%*([[:space:]])}
+ echo -e "==$ANS=="
+ case "$ANS" in
+ OK)
+ echo "Registration succeeded. An email will be sent to the email" \
+ "address you specified."
+ ;;
+ NU)
+ echo "*** ERROR: Registration failed. A user with the chosen username" \
+ "already exist."
+ exit 1
+ ;;
+ *)
+ echo "*** ERROR: Registration failed with error code \"$ANS\""
+ exit 1
+ ;;
+ esac
+else
+ echo "Aborted!"
+fi
+
+exit 0
diff --git a/src/svxlink/modules/frn/multirate_filter_coeff.h b/src/svxlink/modules/frn/multirate_filter_coeff.h
new file mode 100644
index 00000000..295b3d98
--- /dev/null
+++ b/src/svxlink/modules/frn/multirate_filter_coeff.h
@@ -0,0 +1,230 @@
+#ifndef MULTIRATE_FILTER_COEFF_INCLUDED
+#define MULTIRATE_FILTER_COEFF_INCLUDED
+
+
+/*
+First stage 48kHz <-> 16kHz (3.5kHz cut-off)
+This is an intermediate filter meant to be used to downsample to 8kHz.
+
+Parks-McClellan FIR Filter Design
+
+Filter type: Low pass
+Passband: 0 - 0.07291666666666666667 (0 - 3500Hz)
+Order: 29
+Passband ripple: 0.1 dB
+Transition band: 0.09375 (4500Hz)
+Stopband attenuation: 60.0 dB
+*/
+static const int coeff_48_16_int_taps = 30;
+static const float coeff_48_16_int[coeff_48_16_int_taps] =
+{
+ -0.001104533022845565,
+ 1.4483111628894497E-4,
+ 0.0030143616079341333,
+ 0.007290576776838937,
+ 0.010111003515779919,
+ 0.007406824406566465,
+ -0.0033299650331323396,
+ -0.019837606041858764,
+ -0.03369491630668587,
+ -0.03261321520115128,
+ -0.006227597046237875,
+ 0.0472474773894006,
+ 0.11741132225100549,
+ 0.18394793387595304,
+ 0.22449383849677723,
+ 0.22449383849677723,
+ 0.18394793387595304,
+ 0.11741132225100549,
+ 0.0472474773894006,
+ -0.006227597046237875,
+ -0.03261321520115128,
+ -0.03369491630668587,
+ -0.019837606041858764,
+ -0.0033299650331323396,
+ 0.007406824406566465,
+ 0.010111003515779919,
+ 0.007290576776838937,
+ 0.0030143616079341333,
+ 1.4483111628894497E-4,
+ -0.001104533022845565
+};
+
+
+/*
+48kHz <-> 16kHz (5.5kHz cut-off)
+
+Parks-McClellan FIR Filter Design
+
+Filter type: Low pass
+Passband: 0 - 0.1145833333333333333 (0 - 5500Hz)
+Order: 49
+Passband ripple: 0.1 dB
+Transition band: 0.05208333333333333333 (2500Hz)
+Stopband attenuation: 60.0 dB
+*/
+static const int coeff_48_16_taps = 50;
+static const float coeff_48_16[coeff_48_16_taps] =
+{
+ -0.0006552324784575,
+ -0.0023665474931056,
+ -0.0046009521986267,
+ -0.0065673940075750,
+ -0.0063452223170932,
+ -0.0030442928485507,
+ 0.0027216740916904,
+ 0.0079365191173948,
+ 0.0088820372171036,
+ 0.0034577679862077,
+ -0.0063356171066514,
+ -0.0145569576678951,
+ -0.0143873806232840,
+ -0.0031353455170217,
+ 0.0143500967202013,
+ 0.0267723137455069,
+ 0.0227432656734411,
+ -0.0007785303731755,
+ -0.0333072891420923,
+ -0.0533991698157678,
+ -0.0390764894652067,
+ 0.0189267202445683,
+ 0.1088868590088443,
+ 0.2005613197280159,
+ 0.2583048205906900,
+ 0.2583048205906900,
+ 0.2005613197280159,
+ 0.1088868590088443,
+ 0.0189267202445683,
+ -0.0390764894652067,
+ -0.0533991698157678,
+ -0.0333072891420923,
+ -0.0007785303731755,
+ 0.0227432656734411,
+ 0.0267723137455069,
+ 0.0143500967202013,
+ -0.0031353455170217,
+ -0.0143873806232840,
+ -0.0145569576678951,
+ -0.0063356171066514,
+ 0.0034577679862077,
+ 0.0088820372171036,
+ 0.0079365191173948,
+ 0.0027216740916904,
+ -0.0030442928485507,
+ -0.0063452223170932,
+ -0.0065673940075750,
+ -0.0046009521986267,
+ -0.0023665474931056,
+ -0.0006552324784575
+};
+
+
+/*
+8kHz <-> 16kHz
+
+Parks-McClellan FIR Filter Design
+
+Filter type: Low pass
+Passband: 0 - 0.21875 (0 - 3500Hz)
+Order: 89
+Passband ripple: 0.1 dB
+Transition band: 0.03125 (500Hz)
+Stopband attenuation: 62.0 dB
+*/
+static const int coeff_16_8_taps = 90;
+static const float coeff_16_8[coeff_16_8_taps] =
+{
+ 4.4954770039301524E-4,
+ -8.268172996066966E-4,
+ -0.002123078315145856,
+ -0.0015479438021244402,
+ 7.273225897575334E-4,
+ 0.0013974534015721682,
+ -7.334976988828609E-4,
+ -0.0019468497129111343,
+ 4.1355600739715313E-4,
+ 0.002536269673526767,
+ 1.5022005765340837E-4,
+ -0.003101672879509627,
+ -9.95458834752388E-4,
+ 0.00354467345212626,
+ 0.0021278523715996304,
+ -0.0037661500010028543,
+ -0.00353539274926452,
+ 0.0036538076631845626,
+ 0.005173997894832533,
+ -0.003092155201519595,
+ -0.006964869006639621,
+ 0.001972228534636602,
+ 0.008799395727660558,
+ -1.908879053321082E-4,
+ -0.01053574038718076,
+ -0.0023470042371114453,
+ 0.011994344679012392,
+ 0.005724529332766167,
+ -0.012958939230749365,
+ -0.010021252057195512,
+ 0.013170597031930194,
+ 0.015338845914920506,
+ -0.012300860896401845,
+ -0.021850249720503187,
+ 0.009887401534293974,
+ 0.029911674274011077,
+ -0.0051694230705885726,
+ -0.04035692286061595,
+ -0.0034027067537959477,
+ 0.05542257393205645,
+ 0.01998932901259646,
+ -0.08281607098012608,
+ -0.0619525333134873,
+ 0.17225790685629527,
+ 0.42471952920395545,
+ 0.42471952920395545,
+ 0.17225790685629527,
+ -0.0619525333134873,
+ -0.08281607098012608,
+ 0.01998932901259646,
+ 0.05542257393205645,
+ -0.0034027067537959477,
+ -0.04035692286061595,
+ -0.0051694230705885726,
+ 0.029911674274011077,
+ 0.009887401534293974,
+ -0.021850249720503187,
+ -0.012300860896401845,
+ 0.015338845914920506,
+ 0.013170597031930194,
+ -0.010021252057195512,
+ -0.012958939230749365,
+ 0.005724529332766167,
+ 0.011994344679012392,
+ -0.0023470042371114453,
+ -0.01053574038718076,
+ -1.908879053321082E-4,
+ 0.008799395727660558,
+ 0.001972228534636602,
+ -0.006964869006639621,
+ -0.003092155201519595,
+ 0.005173997894832533,
+ 0.0036538076631845626,
+ -0.00353539274926452,
+ -0.0037661500010028543,
+ 0.0021278523715996304,
+ 0.00354467345212626,
+ -9.95458834752388E-4,
+ -0.003101672879509627,
+ 1.5022005765340837E-4,
+ 0.002536269673526767,
+ 4.1355600739715313E-4,
+ -0.0019468497129111343,
+ -7.334976988828609E-4,
+ 0.0013974534015721682,
+ 7.273225897575334E-4,
+ -0.0015479438021244402,
+ -0.002123078315145856,
+ -8.268172996066966E-4,
+ 4.4954770039301524E-4
+};
+
+
+#endif /* MULTIRATE_FILTER_COEFF_INCLUDED */
diff --git a/src/versions b/src/versions
index 22a887e1..aa2dd720 100644
--- a/src/versions
+++ b/src/versions
@@ -18,6 +18,7 @@ MODULE_TCL_VOICE_MAIL=1.0.0
MODULE_SELCALLENC=1.0.0
MODULE_DTMF_REPEATER=1.0.1
MODULE_METARINFO=1.0.1
+MODULE_FRN=1.0.0
# Version for the RemoteTrx application
REMOTE_TRX=1.2.0