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