Merge pull request #126 from sh123/module_frn

Add support for FRN - The Free Radio Network

The new module Frn add support for the [Free Radio Network](http://www.freeradionetwork.eu/).
This commit is contained in:
Tobias Blomberg 2015-12-19 11:45:50 +01:00
commit 852c32e1b9
17 changed files with 3041 additions and 0 deletions

View File

@ -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

View File

@ -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.

View File

@ -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
<ON>[CallsignAndUser]</ON>\\
.br
<EA>[EMailAddress]</EA>\\
.br
<BC>[BandAndChannel]</BC>\\
.br
<DS>[Description]</DS>\\
.br
<NN>[Country]</NN>\\
.br
<CT>[CityCityPart]</CT>' | 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) <sm0svx at users dot sourceforge dot net>
.
.SH "SEE ALSO"
.
.BR svxlink.conf (5)

View File

@ -8,3 +8,4 @@ add_subdirectory(dtmf_repeater)
add_subdirectory(metarinfo)
add_subdirectory(propagation_monitor)
add_subdirectory(selcallenc)
add_subdirectory(frn)

3
src/svxlink/modules/frn/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/makefile.root
/depend
/Makefile

View File

@ -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
)

View File

@ -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
#

View File

@ -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

View File

@ -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 <stdio.h>
#include <iostream>
#include <sstream>
/****************************************************************************
*
* Project Includes
*
****************************************************************************/
#include <AsyncConfig.h>
#include <AsyncAudioSplitter.h>
#include <AsyncAudioValve.h>
#include <AsyncAudioSelector.h>
#include <AsyncAudioFifo.h>
#include <AsyncAudioJitterFifo.h>
#include <AsyncAudioDecimator.h>
#include <AsyncAudioInterpolator.h>
/****************************************************************************
*
* Local Includes
*
****************************************************************************/
#include <version/MODULE_FRN.h>
#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
*/

View File

@ -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 <string>
/****************************************************************************
*
* Project Includes
*
****************************************************************************/
#include <Module.h>
#include <version/SVXLINK.h>
/****************************************************************************
*
* 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
*/

View File

@ -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 <cassert>
#include <cstring>
#include <cstdlib>
#include <sigc++/bind.h>
#include <sstream>
#include <regex.h>
/****************************************************************************
*
* Project Includes
*
****************************************************************************/
#include <AsyncConfig.h>
#include <AsyncAudioPacer.h>
#include <AsyncAudioSelector.h>
#include <AsyncAudioPassthrough.h>
#include <AsyncAudioFifo.h>
#include <AsyncAudioDecimator.h>
#include <AsyncAudioInterpolator.h>
#include <AsyncAudioDebugger.h>
#include <AsyncTcpClient.h>
#include <AsyncTimer.h>
/****************************************************************************
*
* 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<int16_t>(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 << "<VX>" << opt_version << "</VX>";
s << "<EA>" << opt_email_address << "</EA>";
s << "<PW>" << opt_dyn_password << "</PW>";
s << "<ON>" << opt_callsign_and_user << "</ON>";
s << "<CL>" << opt_client_type << "</CL>";
s << "<BC>" << opt_band_and_channel << "</BC>";
s << "<DS>" << opt_description << "</DS>";
s << "<NN>" << opt_country << "</NN>";
s << "<CT>" << opt_city_city_part << "</CT>";
s << "<NT>" << opt_net << "</NT>";
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<float>(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("<AL>BLOCK</AL>") == std::string::npos &&
line.find("<AL>WRONG</AL>") == 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
*/

View File

@ -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 <string>
#include <vector>
#include <sigc++/sigc++.h>
/****************************************************************************
*
* Project Includes
*
****************************************************************************/
extern "C" {
#include <gsm.h>
}
#include <AsyncAudioSink.h>
#include <AsyncAudioSource.h>
#include <AsyncTcpConnection.h>
/****************************************************************************
*
* 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<std::string> 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<void> error;
/**
* @brief QSO internal state has been changed
* @param QSO state enum value
*/
sigc::signal<void, State> 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<void, const FrnList& > frnListReceived;
/**
* @brief Emitted when FRN client list is fully populated
*
* @param vector of strings populated from FRN
*/
sigc::signal<void, const FrnList& > frnClientListReceived;
/**
* @brief Emitted when started receiving voice from client
*
* @param string with client xml data
*/
sigc::signal<void, const std::string& > 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
*/

View File

@ -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 <sstream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
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
*/

View File

@ -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

View File

@ -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 <FRN module config file>"
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:<ON>${CALLSIGN_AND_USER}</ON><EA>${EMAIL_ADDRESS}</EA><BC>${BAND_AND_CHANNEL}</BC><DS>${DESCRIPTION}</DS><NN>${COUNTRY}</NN><CT>${CITY_CITY_PART}</CT>"
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

View File

@ -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 */

View File

@ -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