merged dash into master

pull/14/head
Tom Early 6 years ago
commit 3f71c3e683

8
.gitignore vendored

@ -1,9 +1,17 @@
*.o
*.d
*.gch
.vscode
qn.cfg
qndtmf
gwys.txt
qnitap
qndvap
qndvrptr
qnlink
qngateway
qnigateway
qnremote
qnvoice
qnrelay
qnmodem

@ -1,11 +1,17 @@
QnetGateway started as g2_ircddb written by Scott Lawson, KI4LKF. Many of Scott's original comments are contained
in the this package.
QnetGateway started as g2_ircddb written by Scott Lawson, KI4LKF.
QnetGateway uses IRCDDB software written by Michael Dirska, DL1BFF. Both Scott and Michael published their code under
version 2 of the GNU General Public License. The current form of QnetGateway would be completely impossible without
Scott and Michael's contribution. Thank you for a great starting point of this current project!
QnetGateway uses IRCDDB software written by Michael Dirska, DL1BFF. Both Scott and
Michael published their code under version 2 of the GNU General Public License. The
current form of QnetGateway would be completely impossible without Scott and
Michael's contribution. Thank you for a great starting point of this current
project!
QnetGateway continues to be published under Version 2 of the GNU General Public License, see the LICENSE file.
Some parts of some QnetGateway programs are also inspired by ircDDBGateway,
DStarRepeater and MMDVMHost by Jonathan Naylor G4KLX, and his copyright appears in
those files that used his ideas and in some cases, use his source code.
QnetGateway continues to be published under Version 2 of the GNU General Public
License, see the LICENSE file.
Tom
n7tae (at) arrl (dot) net

@ -1,138 +0,0 @@
Creating a DVAP or a Version 1 DVRPTR hotspot based on a Raspberry Pi or a BeagleBone
Black that can connect to both DStar reflectors as well as XREF reflectors based on
QnetGateway software is easy.
Start with a Raspberry Pi with the latest Raspbian image (see http://raspberrypi.org)
or most any other computer with the latest Debian-based image. For this latest version
of QnetGateway requires the c++ compiler of at least version 4.9.
Even if you are building an MMDVMHost-based hot-spot, the executables and their
source code are very small, so you can get away with a system with minimum storage
capacity. We have build gui-less versions on two gigabyte partitions!
On the RPi, do "sudo raspi-config" and expand the partition, change the password
for the 'pi' user and do any other configuration setup. You don't need to overclock
the RPi for QnetGateway, the default clock rate is just fine.
If you are using a DVAP Dongle or a DVRPTR_V1, login and plug in your device to see
if the OS is recognizing it. The kernel should auto load drivers and you will see
that with the "lsusb" command. The DVAP uses a FTDI chip and the DVRPTR uses Atmel.
If you don't see an approprite output from "lsusb" after your device is plugged in,
you need to enable it by executing:
sudo depmod
sudo modprobe <YOURDEVICEMODULE>
where YOURDEVICEMODULE is "ftdi_sio" for the DVAP or is "cdc_acm" for the DVRPTR.
After a reboot you should see the device in the "lsusb" list. If you don't see
the device listed, QnetGateway software will never be able to open it either.
You will need several packages to build the QnetGateway gateway. You will probably
already have all or most of these but it still doesn't hurt to be sure:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install make g++ unzip git libconfig++-dev
If you are building a QnetGateway + MMDVMHost system, please use the instructions
in the MMDVM.README file. If you are building a QnetGateway for a DVAP or a
DVRPTR_V1 read on...
git clone git://github.com/n7tae/QnetGateway.git
This will create a QnetGateway directory with everything you need to build a dvap or
dvrptr ircddb gateway.
The first thing to do is change to the build directory with "cd QnetGateway" and then
type "make" to build all the QnetGateway executables. If you need DTMFS then also
execute "make g2link_test".
Next, create your qn.cfg configuration file. There are two example for you to look
at:
. qn.everything.cfg contains all parameter with lengthly comments about what
each parameter does. The definitions that are commented out are defined with
their default value.
. qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have
a 70cm DVAP rename the module to "b" and change the frequency.
Remeber the everything file contain detailed comments about all of the values you
can set. Just read through it and edit accordingly. In the end you will need
a configuration file called "qn.cfg".
Additional information about the configuration as well as other important and
useful features are also in the CONFIGURING file.
The gwys.txt file is the internet address and port numbers for any gateway you
would like your ircddb gateway to be able to connect to. The one delivered with
this package is special: It has only DCS reflectors, X-reflectors and DStar
reflectors and the X-reflectors are configured with the 20001 port instead of the
default 30001 prot. This will allow you to connect to these XREF reflectors without
creating any port-forwarding rules on your home router. You will also want to move
X-reflectors to port 20001 if you are going to tether you device via WiFi to a
smart-phone for internet access. Most phone companies will not let you configure
port-forwarding rules on you phone internet account. If you operate behing a
router/firewall without port forwarding in place, you will not be able to
do most callsign routing techniques discussed in CONFIGURING.txt, but you should
still be able to connect to reflectors. You will be able to route to the new
smart-group-server if you are mobile. They have solved the "mobile routing
problem"!
There are MANY OTHER gateways to which you can connect. Executing get_gwys_list.sh
will download a HUGE list of reflectors and gateways from www.va3uv.com with port
address that may need port-forwarding to your sytem. I have provided anotherscript,
'get_reflectors.sh' that will download the same list from va3uv.com, but filter it
so that it only contains DCS x-reflectors (DCSXXX), DStar reflectors (REFXXX) and
X-reflectors (XRFXXX) and it will put all x-reflectors on port 20001 so you
probably won't need any port-forwarding on your home router.
There is another script, reflist.sh, that will download REF, XRF and DCS reflectors
from another source. This is probably the preferred method to getting a gwys.txt
file.
Based on the above discussion, execute either "./reflist.sh", "./get_reflectors.sh" or
"./get_gwy_list.sh".
If you plan on using DTMFS, you can also edit proc_qnlinktest to add new
dtmfs commands.
Then install your system. you have two choices, either DVAP or DVRPTR_V1 by
typing "sudo make installdvap" or "sudo make installdvrptr", respectively.
This should get you up and running. It will take a few minutes for QnetGateway
to get fully connected to the IRCDDB network.
Finally, if you want/need DTMFS, type "sudo make installdtmfs".
The service scripts in /lib/systemd/system and everything else in /usr/local:
The executables will be in /usr/local/bin and the qn.cfg file and other data
will be in /usr/local/etc.
If you find that you need to modify the configuration file, remember that the
installed systems read the /usr/local/etc/qn.cfg file, NOT THE ONE IN YOUR
BUILD DIRECTORY. To keep them synced, it is good admin practice to modify the
qn.cfg file in your build directory and then do a "sudo make uninstall<dvap or
dvrptr>" followed by an "sudo make install<dvap or dvrptr>.
If you are having trouble connecting, use journalctl to view the log output
of each process. "sudo journalctl -u <service name> will show the log from that
service. The QnetGateway service is qngateway, QnetLink is qnlink, QnetDVAP is
qndvap and QnetDVRPTR is qndvrptr.
The beginning of each log file will report the values of all the configuration
parameters (even the ones you didn't specify in qm.cfg) and after that
you will see the verbose reports of what each service is doing. These logs are
invaluable for traking down problems with your qn.cfg file. You can see in real
time what is being added to the logs during operation appending "-f" to the
journalctl command.
You can clean up the build directory of intermediate *.o files with "make clean"
or, you can remove the intermediate *.o files and binary executables with "make
realclean". Note that "make realclean" only removes the binary files from your
build directory and not the copies you installed into /usr/local/bin with the
"sudo make install..." command.
If you want to uninstall everything return to the build directory and type either
"sudo make unistalldvap" or "sudo make uninstalldvrptr" and possibly "sudo make
uninstalldtmfs". This will shutdown the services and remove the service scripts
and everything from /usr/local.
Tom Early, n7tae (at) arrl (dot) net

@ -0,0 +1,168 @@
Creating a hotspot based on a Raspberry Pi or a BeagleBone Black that can connect to
XRF and DCS reflectors as well as XREF reflectors based on QnetGateway software is
easy.
Start with a Raspberry Pi with the latest Raspbian image (see http://raspberrypi.org)
or most any other computer with the latest Debian-based image. For this latest version
of QnetGateway requires the c++ compiler of at least version 4.9.
Even if you are building an MMDVMHost-based hot-spot, the executables and their
source code are very small, so you can get away with a system with minimum storage
capacity. We have build gui-less versions on two gigabyte partitions! You don't need
to overclock the RPi for QnetGateway, the default clock rate is just fine.
If you are using a DVAP Dongle, a DVRPTR_V1, an MMDVM modem or connecting to an Icom
Terminal and Access Point enabled radio, login and plug in your device to see if the OS
is recognizing it. The MMDVM modem will either plug right onto the Raspberry Pi header,
or with an appropriate adaptor, it can be plugged into a USB port. The kernel should
auto load drivers and you will see that with the "lsusb" command. The DVAP and the Icom
radio digital-to-serial cable uses a FTDI chip and the DVRPTR uses Atmel. If you don't
see an appropriate output from "lsusb" after your device is plugged in, you need to
enable it by executing:
sudo depmod
sudo modprobe <YOURDEVICEMODULE>
where YOURDEVICEMODULE is "ftdi_sio" for the DVAP or is "cdc_acm" for the DVRPTR.
After a reboot you should see the device in the "lsusb" list. If you don't see
the device listed, QnetGateway software will never be able to open it either.
You will need several packages to build the QnetGateway gateway. You will probably
already have all or most of these but it still doesn't hurt to be sure:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install make g++ unzip git
If you are building a QnetGateway + MMDVMHost system, please use the instructions
in the MMDVM.README file in order to setup and start your MMDVMHost.
If you are building a QnetGateway for an Icom repeater, you'll need a different
git repository. Please look for QnetICOMGateway.
If you are building an MMDVM, DVAP, DVRPTR_V1, using an MMDVM modem but don't need
any mode besides D-Star or you are using an ICOM Access Point or Terminal Mode
gateway, start by cloning this repository.
git clone git://github.com/n7tae/QnetGateway.git
This will create a QnetGateway directory with everything you need to build your
Gateway system.
The first thing to do is change to the build directory with "cd QnetGateway" and then
define and create your configuration file, qn.cfg:
./qnconfig
This will start a menu driven script. In this mode, only the most common, useful
parameters are available. If you want access to absolutely every parameter use:
./qnconfig expert
Please be sure you know what you are doing if you change these normally hidden parameters.
The configuration script will show you the default value for a parameter. For most cases
the default value is fine, so your qn.cfg file will usually be rather small.
At a minimum, you need to specify an IRCDDB login (please use your legal callsign) and
at least one module. By convention, specify a 23cm module on A, a 70cm module on B and
a 2M module on C. QnetGateway supports a maximum of three modules.
The configure script will show you the default values of every parameter the
QnetGateway programs use. In most cases, the defaults are just fine and you don't
need to override them with your own values. If you need to override the default value,
the general method is to type the key followed by the new value. If you want to delete
an overridden value, type a "u" followed by the key you want to unset. Boolean values
(true or false) can be toggled by just entering the key.
By default, QnetGateway will only connect to the IPv4 QuadNet server at rr.openquad.net.
QnetGateway is capabile of dual-stack operation and can also connect to the IPv6 server
at rrv6.openquad.net. If you want to operate in dual stack mode, enter the IRC sub-menu
and set ha to "rrv6.openquad.net" and hb to "rr.openquad.net".
Once your operating in dual-stack mode, any routing will prefer an IPv6 address, if
available. It's that easy.
For a DVAP Dongle or a DVRPTR V1, there are some parameters that must be specified,
like the serial number. The two "inverse" parameters for the DVRPTR will probably
have to change. If you don't hear traffic on a connected system, try toggling the
dvrptr_inverse_rx parameter. If you aren't heard, toggle the dvrptr_inverse_tx
parameter. For the DVAP Dongle, or if you are using an MMDVM modem without
MMDVMHost, you need to specify the operational frequency. You also need to make
sure the device address is correct for the MMDVM modem. "Hats" connected to the
Raspberry Pi header can usually be found at /dev/ttyAMA0 while device plugged into
a USB port are usually at /dev/ttyUSB0.
If you want to use the closed-source, legacy D-Plus reflectors and repeaters, you
need to make sure you are authorized to use those systems. They require that you
are a registered user, see www.dstargateway.org for more information. If you are
a registered user, you can enable QnetGateway to used this closed-source system
by DPlus. By default, when QnetLink registers your callsign you will download
both repeaters and reflectors. You can control this with configuration parameters.
You can also control the priority of D-Plus reflectors and repeaters. By default,
QnetLink first loads the gwys.txt file first and then the systems delivered by the
D-Plus Authorization system. You can change the order, if you want definitions
in your gwys.txt file to override any delivered by the D-Plus authorization server.
The information downloaded from the DPlus server is dynamic and will change
from hour to hour. You can update QnetLink by sending " F" to your system.
This will purge the current table and re-authorize with the DPlus server and
then reload gwys.txt.
Because of the way DPlus authorization works, QnetLink can't actually confirm
that your authorization was successful. If your system is unlinked after trying
to transmit into a DPlus system, it means that your authorization was
unsuccessful. This might indicate that their may be a problem with your
DPlus registration.
If you want to see what's going on inside the programs, there are logging options
available that will cause a lot more log entries. These extra entries can be
useful if you are trying to track down problems with your system.
After you are happy with your configuration, be sure to write it out with the 'w'
key in the main menu. It will show you your qn.cfg file after it writes it out.
After you install and try out your system, you may find that you need to change
some configuration values. In that case just start the configure script again.
It will read the current qn.cfg file when it start and initialize the menus
accordingly.
Of course, you can always build your own qn.cfg file. There are three example for
you to look at:
. qn.everything.cfg contains all parameter with comments about what
each parameter does. The definitions that are commented out are defined with
their default value.
. qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have
a 70cm DVAP rename the module to "b" and change the frequency.
. qn.mmdvm.cfg is the starting place for configuring an MMDVMHost repeater. Be sure
the module assignment agrees with the module configured in your MMDVM.cfg file.
. qn.itap.cfg is a simple configuration file for Icom's Terminal and Access Point
Mode. Please read ITAP.README for more information.
Once you have your qn.cfg file, your ready to compile and install your system, type:
./qnadmin
The first thing you want to do is to create your gwys.txt file. use the 'gw' key
to get into that sub-menu. There are several choices to initialize your gwys.txt
file. Choose one and then you can edit the file to your satisfaction.
You are now ready to install your QnetGateway system. If you are installing an
MMDVM-based system, follow the instructions in MMDVM.README to get MMDVMHost
up and running.
Now, you can compile and install your system with the 'is' key. qnadmin will
use your qn.cfg file to figure out what needs to be compiled and how it is to
be installed.
If you plan on using DTMF, use the 'id' key to install it. Once you install
something, the qnadmin will dynamically change and show you how to uninstall the
installed items.
The maintenance sub-menu accessed with the 'm' key will let you stop and start
different programs in your installed package. Note that this just uses
systemctl to start and stop a service. It will not uninstall the service.
You might want to do this if you have changed your configuration file.
The log sub-menu accessed with the 'l' key can be use to put a "tail" on different
log files and you can watch a log file in real-time.
Tom Early, n7tae (at) arrl (dot) net

@ -1,400 +0,0 @@
/*
* Copyright (C) 2010, 2011, 2012 by Scott Lawson KI4LKF
* addition Copyright (C) 2018 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
I have substantially modified (and hopefully simplified) the configuration
of this software, now, modules QnetGateway, QnetLink, QnetDVAP and QnetDVRPTR
all use a single configuration file. Further, and this is the exciting
part, NEARLY ALL of the parameters these modules need have a useful
default value. If the default value is acceptible to you (and it
probably will be) then you only need to specify what you need to change.
This means that for most users, you will only need to specify a few
parameters. For example, if you want to set up a 70cm dvap, your working
configuration file might be:
-------------------------------------------------------------------
# my configuration, using rr.openquad.net with my DVAP Dongle
# on startup link module b to REF020C
ircddb = {
login = "XX0XXX"
}
module = {
b = {
type = "dvap"
frequency = 445.474
serial_number = "AP123456"
}
}
link = {
link_at_start = "CREF020A"
admin = [ "XX0XXX"]
}
-------------------------------------------------------------------
Of course, you can add other parameters, such as latitude and longitude,
or a URL that will show up on the "User Gateways" page of
www.openquad.net. This software is highly flexible, so you can have
different modules running on different computers and these hidden
configuration parameters to allow that are there, waiting to be defined.
However, most hams won't have to bother with them!
Some other features are discussed below and are pretty much directly
quoted from KI4LKF original documentation.
Tom Early, n7tae (at) arrl (dot) net
G2_ircDDB adapted from the OpenG2
DESCRIPTION
=================
QnetGateway is a Dstar G2 gateway for the Dstar network with ircDDB routing
and USroot routing. It runs on Linux(as a Linux service).
ircddb originated from the OpenG2 with only one change.
Instead of using the local Postgres database server,
we use the remote IRC database server.(group2 or group1)
So, the difference between OpenG2 and ircddb is 0.01%
This new software QnetGateway has been approved for use on the ircDDB network.
IRC Gateways such as g2_ircdb (other IRC gateways copied from our OpenG2)
connect to group2 server(USA/Canada) or group1 server(Europe).
What are these group1, group2 servers? Group1 and group2 servers are really
the combination of 2 programs running together:
These are:
---- An IRC (Internet Relay Chat) program, in this case it is the inspIRCd
program
---- the LDAP (Light Directory Access Protocol) program
So, ircDDB database servers are NOTHING new, they were designed by groups
of people 20-30 years ago, before even the ICOM Dstar came out.
The German IRC team, copied the above programs (IRC, LDAP) from those groups,
and they called it group1 and group2.
The reason for that was that an IRC(Internet Relay Chat) server, sends data
immediately, while the ICOM dstar network sends data 5 minutes later after a
user keyed up a Dstar repeater. So, IRC is faster, but it overloads your
home-network.
Using a PERSONAL callsign to set up an ircDBB gateway
=====================================================
In qn.cfg, set OWNER equal to your personal callsign.
In QnetLink.cfg, set OWNER equal to your personal callsign.
In your repeater config file, set OWNER equal to your personal callsign.
Using a REPEATER callsign to set up an ircDBB gateway
=====================================================
In QnetGateway.cfg, set OWNER equal to a REPEATER callsign (that you received
from the ham authority of your Country).
In QnetLink.cfg, set OWNER equal to a REPEATER callsign (that you received
from the ham authority of your Country) In your repeater config file, set
OWNER equal to a REPEATER callsign (that you received from the ham authority
of your Country).
However, since the IRC database servers(group2 in Canada/USA, group1 in
Europe) do NOT allow personal callsigns to be used as gateways, then you may
be asking what is the point?
Here is the logic:
In some Countries, it is very difficult to receive a GATEWAY (club/group)
callsign, sometimes it takes years. In cases such as these, hams only have
their own personal callsign to use. So, how do we get around the problem when
the IRC servers group1, group2 require a GATEWAY callsign, but the user only
has a PERSONAL callsign?
Some groups of hams got together and installed their own IRC database server.
An IRC database server (like group1, group2) is really these 2 basic programs:
---- inspIRCd server software which is an IRC server (Internet Relay Chat)
which was designed in 1988. One such IRC program is inspIRCd which was
chosen by the German IRC team for use in their group1, group2 installations.
--- LDAP server software, which is the Light Directory Access Protocol server,
that was written in 1993.
These 2 programs inspIRCd + LDAP make up the the IRC database servers as we know
them today and were installed together by the IRC team on group1, group2
installations.
That is all there is to it.
When LDAP runs, it checks the Gateway password as listed in QnetGateway.cfg:
IRC_PASS=... When LDAP does NOT run, then the IRC_PASS is NOT checked.
So, some groups of hams, have installed the inspIRCd server, without installing
the LDAP server. In such an installation, then the IRC_PASS is NOT checked, so
you can use your personal callsign.
g2_ircDDB supports the following commands in YRCALL
Note: In the commands that folow, _ is a SPACE.
1)
For Echotest/playback.
YRCALL=_ _ _ _ _ _ _E
2)
For Voice Mail:
YRCALL=_ _ _ _ _ _ S0
The above command will Store/create voice mail in the dvtool file
x_voicemail.dat.
YRCALL=_ _ _ _ _ _ R0
The above command will Recall/playback voice mail from the dvtool file
x_voicemail.dat.
YRCALL=_ _ _ _ _ _ C0
The above command will Clear/delete voice mail. File x_voicemail.dat will be
deleted. In all cases, the letter x in the file name x_voicemail is the
module A,B or C.
3)
For inquiring the status of the link:
YRCALL=_ _ _ _ _ _ _I
4)
For unlinking:
YRCALL=_ _ _ _ _ _ _U
5)
For linking:
YRCALL=XXNYYYML
Where XXNYYY is a friendly gateway, M is the gateways's module and L is the
LINK command.
YRCALL=XRFNNNML
Where XRFNNN is a friendly reflector, M is the reflector's module and L is
the LINK command.
Note about linking:
After linking succeeds, set YRCALL=CQCQCQ because your audio will go to the
remote reflector ONLY if YRCALL=CQCQCQ.
6)
For executing scripts:
YRCALL=_ _ _ _ _ _ nX
where n can be from 0-9 or A-Z.
Example: YRCALL=_ _ _ _ _ _1X
Then the script exec_1.sh will be executed.
Two scripts, exec_R.sh and exec_H.sh are included to reboot and halt your
system, respectively. Also note that rpt1 is passed to these scripts\
so you can use this as an input parameter for your scripts.
Only admins can execute scripts, so set QnetLink.admin to your callsign
7)
Enabling and disabling INCOMING HotSpotNode connections:
To Enable:
YRCALL=_ _ _ _ _ _ D1
To Disable:
YRCALL=_ _ _ _ _ _ D0
Required software to run the QnetGateway gateway correctly:
--- QnetGateway: The G2 audio gateway.
--- QnetLink: This communicates with QnetGateway to link the local G2 gateway
to reflectors. Note: QnetLink is NOT required if you only make
routing calls or talk locally on the repeater.
--- rptr: This is our dstar repeater software that uses a GMSK adapter/modem.
Instead of rptr, you can use our QnetDVAP dstar repeater software
which uses a DVAP device. Intead of rptr, you can use our QnetDVRPTR
dstar repeater software which uses the DV-RPTR modem(dg1ht).
ROUTING methods
===============
Some Dstar routing examples follow. Please do not use the same data because
KJ4NHF is one of our own Dstar repeaters, and KI4LKF is a personal callsign
in our group.
Example of ZONE routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to reach remote gateway XXNYYY module C
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=/XXNYYYC
RPT1=KJ4NHF B
RPT2=KJ4NHF G
Example of CALLSIGN routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to talk to user XX0XXX
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=XX0XXX
RPT1=KJ4NHF B
RPT2=KJ4NHF G
Example of Cross-Band routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to talk from your local module B to your local module C
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=CQCQCQ
RPT1=KJ4NHF B
RPT2=KJ4NHF C
DTMF decoding and processing
=============================
Prepare the software to decode and process DTMF tones
-----------------------------------------------------
Edit the Shell script proc_qnlinktest.sh
Correct the value for G2 to be the local G2 gateway
callsign(same value in qn.cfg ----> OWNER)
Edit G2_INT_IP as follows:
If your QnetGateway.cfg, says "G2_INTERNAL_IP=0.0.0.0" then set
G2_INT_IP=127.0.0.1.
If your QnetGateway, says G2_INTERNAL_IP=<real local IP address>
then set G2_INT_IP equal to the exact value of G2_INTERNAL_IP.
Edit G2_INT_PORT to be equal to G2_INTERNAL_PORT in QnetGateway.cfg.
Note: When local RF user has entered dtmf tones on the Dstar HT
and then PTT is released, QnetGateway will print the whole
sequence in the QnetGateway.log just before it creates the
dtmf file under /tmp.
How to enter DTMF tones correctly on your Dstar HT
--------------------------------------------------
If you want to have perfect DTMF decoding/processing in QnetGateway,
follow these suggestions:
1) Hold down each dtmf key for at least 150 milliseconds.
2) Leave at least 250 milliseconds of "silence", when you go from
one dtmf key to the next dtmf key.
If you use a DTMF autodialer on your Dstar radio, you can program
the above numbers easily into the ICOM's radio "auto-dialer".
Note: MAXIMUM dtmf sequence is up to 32 dtmf tones.
What dtmf tones are being decoded and processed
-----------------------------------------------
To link to an XRF reflector: Example: B02102
That says: link to XRF021 module B. So, we use the # dtmf key
to mean that we're interested in linking to an XRF reflector. The
last two digits are the remote module 01 thru 05 (which is
translated to A thru E).
To link to a DCS reflector: Example: D00126
That says: link to DCS001 module Z. So, we use the D dtmf key
to mean that we're interested in linking to a DCS reflector. The
last two digits are the remote module 01 thru 26 (which is
translated to A thru Z).
To link to REF: Example: *01601
That says: link to REF016 module A. So, we use the * dtmf key
to mean that we're interested in linking to a REF reflector. The
last two digits are the remote module 01 thru 05 (which is
translated to A thru E).
To unlink (from any reflector xrf or ref): #
To get the "link status": 0 or 00
Note:
You can extend the shell script to do more things. like force
your repeater to ID itself. Any YRCALL command that can be executed
by g2link_test, can be added to the shell script. Basically, the
Linux shell script proc_QnetGateway_dtmfs.sh converts the decoded
dtmf tones into YRCALL commands using g2link_test program.
===========
QnetLink is a small program that is used to link a local RF repeater
band to a remote reflector. QnetLink software is used by our QnetGateway
(an IRCddb gateway) and by our g2_ccs (a CCS gateway).
Before we begin, there are some dat files included in the QnetLink
ZIP package. These dat files are:
already_linked.dat
already_unlinked.dat
failed_linked.dat
linked.dat
unlinked.dat
id.dat
All of the above dat files are dvtool format files and they are used
when certain events take place. For example, when the link between your
gateway and a reflector is established, then the file "linked.dat" will
be played over RF, so anyone listening on your repeater, will hear the
announcement that the link has been established. You do not have to
change these files, unless you want to have your own personal voice
played over RF. These dat files were created using a computer and they
are NOT anyone's voice.
The only dat file most hams will need to change is id.dat. The file
id.dat contains the audio "UNLINKED" and nothing else. When the gateway
is not linked and the RF user sets YRCALL=_______I to request the
status of the link, the file id.dat will be played back over RF. But
you should create your own id.dat file that should identify your own
repeater with extra information if you like.
For example, you could create your own dvtool audio file that contains
the audio: "This is repeater ...". A simple way to create your own
recorded "repeater identification file" id.dat is to use your Dstar HT
and set YRCALL command: YRCALL=______S0 and key up your repeater.
Start talking, and the gateway will record your audio into the file
x_voicemail.dat where x is one of A.B or C. Now copy that file:
sudo cp -f /tmp/C_voicemail.dat /usr/local/etc/it.dat
You may be thinking why did we put all this source code inside QnetLink
instead of putting it all inside the Gateway software QnetGateway (or
g2_ccs CCS gateway software). Because it is NOT a good design to burden
the G2 gateway with code that is not DSTAR protocol and reflectors are
NOT part of Dstar and they are not Dstar protocol.
The only code that should be in a gateway, is G2 protocol code. This way
we keep the gateway (QnetGateway) clean of any unneccesary source
code logic and containing only pure G2/D-Star logic.
QnetDVAP
The serial number required in the .cfg file is obtained from the label
that can be seen on the DVAP circuit board. In most cases set the power
to maximum level.
QnetDVRPTR
The serial number required in the qn.cfg file can most easily be obtained
by examining the /var/log/QnetDVRPTR.log file once the board has been
powered up by the BBB or RasPi. Once you know the board serial number,
edit /usr/local/etc/g2.cfg. Please note that once installed, you
need to edit the configuration files in /usr/local/etc, not where you
build the software. You need to be root to edit files in /usr/local/etc.
After editing /usr/local/etc/g2.cfg file you can restart the effected
program with "sudo service QnetDVRPTR restart". Or you can alway just
reboot with "sudo reboot".
Rig specific parameters are in "module.x.rf_rx_level", "module.x.inverse.rx" and
"module.x.inverse.tx". You need to play with these to work best with your rig.
With my Kenwood TM-V71, I use the default values.
You can first start with inverse.rx, trying true or false. Use the echo command
on your radio and look in the logs to see if you are getting into the
dvprtr. Remember to restart the QnetDVRPTR service anytime you modify
/usr/local/etc/g2.cfg. Once you are able to get into the QnetDVRPTR,
then you can work out the inverse.tx paramter. Once you hear anything
with in the echo mode, move rf_rx_level up or down to get the best
audio.
Once you have a working system, it's a good idea to back up you qn.cfg
files.

@ -0,0 +1,141 @@
/*
* Copyright (C) 2010-2015 by Jonathan Naylor G4KLX
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string>
#include <cassert>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <thread>
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "DPlusAuthenticator.h"
CDPlusAuthenticator::CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address) :
m_loginCallsign(loginCallsign),
m_address(address)
{
assert(loginCallsign.size());
Trim(m_loginCallsign);
}
CDPlusAuthenticator::~CDPlusAuthenticator()
{
}
bool CDPlusAuthenticator::Process(std::map<std::string, std::string> &gwy_map, const bool reflectors, const bool repeaters)
// return true if everything went okay
{
int result = client.Open(m_address, AF_UNSPEC, "20001");
if (result) {
fprintf(stderr, "DPlus Authorization failed: %s\n", gai_strerror(result));
return true;
}
return authenticate(gwy_map, reflectors, repeaters);
}
bool CDPlusAuthenticator::authenticate(std::map<std::string, std::string> &gwy_map, const bool reflectors, const bool repeaters)
{
unsigned char* buffer = new unsigned char[4096U];
::memset(buffer, ' ', 56U);
buffer[0U] = 0x38U;
buffer[1U] = 0xC0U;
buffer[2U] = 0x01U;
buffer[3U] = 0x00U;
::memcpy(buffer+4, m_loginCallsign.c_str(), m_loginCallsign.size());
::memcpy(buffer+12, "DV019999", 8);
::memcpy(buffer+28, "W7IB2", 5);
::memcpy(buffer+40, "DHS0257", 7);
if (client.Write(buffer, 56U)) {
fprintf(stderr, "ERROR: could not write opening phrase\n");
client.Close();
delete[] buffer;
return true;
}
int ret = client.ReadExact(buffer, 2U);
size_t sofar = gwy_map.size();
unsigned int returned = 0;
while (ret == 2) {
unsigned int len = (buffer[1U] & 0x0FU) * 256U + buffer[0U];
// Ensure that we get exactly len - 2U bytes from the TCP stream
ret = client.ReadExact(buffer + 2U, len - 2U);
if (0 > ret) {
fprintf(stderr, "Problem reading line, it returned %d\n", errno);
return true;
}
if ((buffer[1U] & 0xC0U) != 0xC0U || buffer[2U] != 0x01U) {
fprintf(stderr, "Invalid packet received from 20001\n");
return true;
}
for (unsigned int i = 8U; (i + 25U) < len; i += 26U) {
std::string address((char *)(buffer + i));
std::string name((char *)(buffer + i + 16U));
Trim(address);
Trim(name);
name.resize(8, ' ');
// Get the active flag
bool active = (buffer[i + 25U] & 0x80U) == 0x80U;
// An empty name or IP address or an inactive gateway/reflector is not added
if (address.size()>0U && name.size()>0U && active) {
returned++;
if (reflectors && 0==name.compare(0, 3, "REF"))
gwy_map[name] = address.append(" 20001");
else if (repeaters && name.compare(0, 3, "REF"))
gwy_map[name] = address.append(" 20001");
}
}
ret = client.ReadExact(buffer, 2U);
}
printf("Probably authorized DPlus on %s using callsign %s\n", m_address.c_str(), m_loginCallsign.c_str());
printf("%s returned %u systems\n", m_address.c_str(), returned);
printf("The gateway map increased by %u additional DPlus gateways\n", (unsigned int)(gwy_map.size() - sofar));
client.Close();
delete[] buffer;
return false;
}
void CDPlusAuthenticator::Trim(std::string &s)
{
auto it = s.begin();
while (it!=s.end() && isspace(*it))
s.erase(it);
auto rit = s.rbegin();
while (rit!=s.rend() && isspace(*rit)) {
s.resize(s.size() - 1);
rit = s.rbegin();
}
}

@ -0,0 +1,40 @@
#pragma once
/*
* Copyright (C) 2010-2013 by Jonathan Naylor G4KLX
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
**/
#include <netinet/in.h>
#include <map>
#include <string>
#include "TCPReaderWriterClient.h"
class CDPlusAuthenticator {
public:
CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address);
~CDPlusAuthenticator();
bool Process(std::map<std::string, std::string> &gwy_map, const bool reflectors, const bool repeaters);
private:
std::string m_loginCallsign;
std::string m_address;
CTCPReaderWriterClient client;
void Trim(std::string &s);
bool authenticate(std::map<std::string, std::string> &gwy_map, const bool reflectors, const bool repeaters);
};

@ -4,19 +4,12 @@ DTMF is available with the QnetGateway Software. You can do things like linking
and unlinking from you radio's keyboard, if present. For example, typing
"B75703" should link you to XRF757 C.
DTMF is not enabled by default. To enable it, first create a DTMF script called
"qndtmf" in the build directory. A complete, functional script is included
and is called "qndtmf.sh", so you can start by just copying it to the target:
DTMF is not enabled by default. You can install and uninstall it from the
qnadmin script.
cp qndtmf.sh qndtmf
Then you can install DTMF: sudo make installdtmf
To uninstall DTMF: sudo make uninstalldtmf
Be sure to look at the script. It contains examples of all the DTMF commands it
supports. You can add more if you are good at shell programming and understand
how qnremote works.
Be sure to look at the 'dtmf' script. It contains examples of all the DTMF
commands it supports. You can add more if you are good at shell programming
and understand how qnremote works.
######## QnetRemote #########
@ -54,32 +47,37 @@ to do this search the web with "linux cron job".
######## QnetVoice ########
QnetVoice is another command line program to send any DVTool-formatted file
to a moule configured on you gateway. It has a simple interface, like
QnetVoice is another command line program to send any ambe-formatted file
to a module configured on you gateway. It has a simple interface, like
QnetRemote. To get started, open a shell on your system and type "qnvoice":
pi@raspberrypi:~ $ qnvoice
Usage: qnvoice <module> <mycall> <dvtoolFile>
Usage: qnvoice <module> <mycall> <dat_file>
Where...
module is one of your modules
mycall is your personal callsign
dvtoolFile is a dvtool file
dat_file is a .dat voice file file
Please note that <module> and <mycall> are not case sensitive, but
<dvtoolFile> is.
<dat_file> is.
So what's a DVTool file? All the voice prompts delivered with QnetGateway,
in the QnetGateway/announce directory and DVTool files. And, you can
So what's a .dat voice file? All the voice prompts delivered with QnetGateway,
in the QnetGateway/announce directory are .dat files. And, you can
easily create your own!
To make your own DVTool file, just put " S0" in your radio's YourCall
and key up and talk. You will be making a "voice mail" DVTool file:
/tmp/X_voicemail.dat, where X is the module on which you are transmitting.
Once you have created it, you can move it out of the /tmp directory and
rename it. Then you can use it in a qnvoice command. You can also replace
all of the standard voice messsages with your own. If you want to do this
put your versions in a directory outside of the build directoy and make
a small script that will copy your messages over the existing, installed
To make your own .dat voice file file, just put " S0" in your radio's
YourCall and key up and talk. You will be making a "voice mail" .dat file:
/tmp/X_voicemail.dat2, where X is the module on which you are transmitting.
Once you have created a voicemail file, you can move it out of the /tmp
directory and rename it, but if you want to use it in a qnvoice command,
you need to strip off the first 56 bytes. You can do this with the command:
tail -c +56 /tmp/X_voicemail.dat2 > mynewvoiceprompt.dat
Then you can use it in a qnvoice command. You can also replace all of the
standard voice messages with your own. If you want to do this put your
versions in a directory outside of the build directory and make a small
script that will copy your messages over the existing, installed
messages:
#!/bin/bash
@ -89,4 +87,3 @@ sudo cp ./id.dat /usr/local/etc
That way, when you pull down a new release and build and install it, you
can quickly update the voice messages with your own.

@ -36,7 +36,7 @@ CDVAPDongle::~CDVAPDongle()
{
}
bool CDVAPDongle::Initialize(char *serialno, int frequency, int offset, int power, int squelch)
bool CDVAPDongle::Initialize(const char *serialno, const int frequency, const int offset, int const power, const int squelch)
{
bool ok = false;
char device[128];
@ -276,7 +276,7 @@ bool CDVAPDongle::syncit()
return false;
}
bool CDVAPDongle::get_ser(char *dvp, char *dvap_serial_number)
bool CDVAPDongle::get_ser(const char *dvp, const char *dvap_serial_number)
{
unsigned cnt = 0;
REPLY_TYPE reply;
@ -301,10 +301,10 @@ bool CDVAPDongle::get_ser(char *dvp, char *dvap_serial_number)
} while (reply != RT_SER);
if (0 == strcmp(dvapreg.param.sstr, dvap_serial_number)) {
printf("Using %s: %s, because serial number matches your dvap_rptr.cfg\n", dvp, dvap_serial_number);
printf("Using %s: %s, because serial number matches your configuration file\n", dvp, dvap_serial_number);
return true;
}
printf("Device %s has serial %s, but does not match your config value %s\n", dvp, dvapreg.param.sstr, dvap_serial_number);
printf("Device %s has serial %s, but does not match your configuration value %s\n", dvp, dvapreg.param.sstr, dvap_serial_number);
return false;
}

@ -66,8 +66,8 @@ typedef struct dvp_register_tag {
union {
struct {
unsigned char flag[3];
unsigned char rpt1[8];
unsigned char rpt2[8];
unsigned char rpt1[8];
unsigned char urcall[8];
unsigned char mycall[8];
unsigned char sfx[4];
@ -88,7 +88,7 @@ class CDVAPDongle
public:
CDVAPDongle();
~CDVAPDongle();
bool Initialize(char *serialno, int frequency, int offset, int power, int squelch);
bool Initialize(const char *serialno, const int frequency, const int offset, const int power, const int squelch);
REPLY_TYPE GetReply(SDVAP_REGISTER &dr);
void Stop();
int KeepAlive();
@ -107,7 +107,7 @@ class CDVAPDongle
int read_from_dvp(void* buf, unsigned int len);
int write_to_dvp(const void* buf, const unsigned int len);
bool syncit();
bool get_ser(char *dvp, char *dvap_serial_number);
bool get_ser(const char *dvp, const char *dvap_serial_number);
bool get_name();
bool get_fw();
bool set_modu();

@ -11,89 +11,73 @@ locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" befor
your start. On a Raspberry Pi, you can do all of this with the configureation menu:
"sudo raspi-config".
1) Install the only external library you need: sudo apt-get install libconfig++-dev
Yeah! No wxWidgets!
If you are using a device that uses the GPIO header on the raspberry pi, you need to
disable the serial0 console in the /boot/cmdline.txt file: Remove the reference to
"console=serial0,115200" in this file. You should also disable bluetooth by adding:
"dtoverlay=pi3-disable-bt" (without the quotes) to the end of the /boot/config.txt.
Now in your home directory...
In your home directory...
2) Clone the MMDVMHost repository: git clone git://github.com/g4klx/MMDVMHost.git
1) Clone the MMDVMHost repository: git clone git://github.com/g4klx/MMDVMHost.git
3) cd into the MMDVMHost directory and compile: make
2) cd into the MMDVMHost directory and compile: make
If you're system has multiple processors, use: make -jx
where x is the number of processors on you system.
To tell how many processors you have: cat /proc/cpuinfo | grep processor | wc -l
4) Copy the ini file template: cp MMDVM.ini MMDVM.qn
3) Copy the ini file template: cp MMDVM.ini MMDVM.qn
5) Edit the MMDVM.qn file. Set your Callsign and Id. Turn off duplex. Enable an
4) Edit the MMDVM.qn file. Set your Callsign and Id. Turn off duplex. Enable an
external display, if you have one. Set the Frequency(s). Set the Latitude and
other location info if you want. Change the log levels. I use DisplayLevel=2
and FileLevel=0. Set the Port on your modem. Disable all services except for
D-Star (at least for now). Very important: Set the [D-Star] Module. For UHF
use B and for VHF use C.
6) Let's test it! Start it: ./MMDVMHost MMDVM.qn
5) Let's test it! Start it: ./MMDVMHost MMDVM.qn
7) Key your radio and see if the MMDVMHost responds with an acknowledgement. Then
6) Key your radio and see if the MMDVMHost responds with an acknowledgment. Then
you'll know that everything on the MMDVM side is fine.
8) Stop the MMDVMHost with a Control-C and edit the MMDVM.qn file again. Change
7) Stop the MMDVMHost with a Control-C and edit the MMDVM.qn file again. Change
the [D-Star] AckReply=0
You won't/don't want this reply from MMDVMhost. You want the acknowledgement
You won't/don't want this reply from MMDVMhost. You want the acknowledgment
from QnetGateway, it's more informative.
We'll come back to MMDVMHost later, but for now let's work on the QnetGateway.
9) cd back to the home directory and clone the QnetGateway:
8) cd back to the home directory and clone the QnetGateway. Type:
git clone git://github.com/n7tae/QnetGateway.git
10) Get into the build directory: cd QnetGateway
9) Get into the build directory. Type: cd QnetGateway
10) You need a configuration file called qn.cfg for QnetGateway. Start the
configuration script: ./qnconfig
This script will allow you to change any configuration parameter from its
default value. At a minimum, you need to set your login callsign in the ircddb
section and define at least one module. If you want to use the legacy,
closed-source D-Plus reflectors and repeaters, be sure to enable it in the
D-Plus menu. Be sure to write out your configruation file after you have entered
all your changes, then you can exit the configuration script.
11) Next, your ready to install your software. For that, start the administration
script. Type: ./qnadmin
First you want to create you gwys.txt file. This file contains a list of
reflectors and repeaters you might like to link. Got into the gwys.txt menu
creation sub-menu with "gw" and select one of three different methods to
generate a gwys.txt file. You can add and/or delete records manually in
this sub-menu. Return to the main menu because your now ready to build and
install your system. Install your configured system with "is". This will
compile and install everything that you have configured.
12) You also need to install your MMDVMHost with "im".
11) Then compile: make
don't forget the -j option!
12) You need a configuration file called qn.cfg for QnetGateway. A good, nearly
working config file is qn.mmdvm.cfg. Copy it to qn.cfg and edit it.
13) You need a gwys.txt file for all the systems to which you may wish to link.
If you want to be able to link to repeaters: ./get_gwy_list.sh
If you are only interested in linking to reflectors: ./reflist.sh
This will download and format your gwys.txt file.
If the reflector(s) or repeater(s) you use most often are not present in the
gwys.txt file, you can add them manually, using the same syntax as the existing
entries, at the end of the file.
If you find you can no longer connect to a system, it may be because its IP
address has changed. You can execute either script again, copy it to
/usr/local/etc, and then: either reboot you system, or put " L" in your
URField and key your radio, or: sudo systemctl restart qnlink
14) We have a gwys.txt file and a qn.cfg in the build directory, so we are ready
to install and start the three QnetGateway services: sudo make install
You should be up and running now! Congratulations! <Fireworks!>
15) Now it's time to start the MMDVMHost service. You'll do this from the
QnetGateway directory you are aleady in: sudo make installmmdvm
This command will fail if it can't find both MMDVMHost and MMDVM.qn
in the ../MMDVMHost directory.
13) There are many additional things that can be done in the administration menu.
Enter the log menu with "l" and from there you can view logs from each
running process.
You should be up and running now! Congratulations! <Fireworks!>
14) DTMF is _not_ enabled by default if you want it, type "id" in the main menu.
16) You can see the log of any of the 4 services that make up the QnetGateway +
MMDVMHost system:
sudo journalctl -u qngateway -f
sudo journalctl -u qnlink -f
sudo journalctl -u qnrelay -f
sudo journalctl -u mmdvm -f
You can do all four of these in one terminal window (that you can detach from!)
by using "screen": sudo apt-get install screen
If you don't know how to use screen: http://aperiodic.net/screen/quick_reference
Being able to detach from a screen session is very useful, especially if you are
operating "headless"!
17) DTMF is _not_ enabled by default if you want it, you need to do two things:
First, create a working DTMF script. In the build directory: cp qndtmf.sh qndtmf
Then, install the DTMF service: sudo make installdtmf
You should be good to go, The DTMF command "00" should announce the linked
status of you module. See DTMF+REMOTE.README for more information.
18) Build and start other MMDVMHost services...
15) Build and start other MMDVMHost services...

@ -1,4 +1,4 @@
# Copyright (c) 2018 by Thomas A. Early N7TAE
# Copyright (c) 2018-2019 by Thomas A. Early N7TAE
#
# 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
@ -30,37 +30,51 @@ CRONDIR=/etc/cron.d
# or, you can choose this for a much smaller executable without debugging help
CPPFLAGS=-W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\"
LDFLAGS=-L/usr/lib -lconfig++ -lrt
LDFLAGS=-L/usr/lib -lrt
DSTROBJS = $(IRC)/dstar_dv.o $(IRC)/golay23.o
IRCOBJS = $(IRC)/IRCDDB.o $(IRC)/IRCClient.o $(IRC)/IRCReceiver.o $(IRC)/IRCMessageQueue.o $(IRC)/IRCProtocol.o $(IRC)/IRCMessage.o $(IRC)/IRCDDBApp.o $(IRC)/IRCutils.o $(DSTROBJS)
SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
PROGRAMS=qngateway qnlink qnrelay qndvap qndvrptr qnremote qnvoice
all : $(PROGRAMS)
ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap qnmodem
BASE_PROGRAMS=qngateway qnlink qnremote qnvoice
qngateway : $(IRCOBJS) QnetGateway.o aprs.o
g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread
all : $(ALL_PROGRAMS)
base : $(BASE_PROGRAMS)
relay : qnrelay
dvap : qndvap
dvrptr : qndvrptr
itap : qnitap
modem : qnmodem
qnlink : QnetLink.o
g++ $(CPPFLAGS) -o qnlink QnetLink.o $(LDFLAGS) -pthread
qngateway : QnetGateway.o aprs.o UnixDgramSocket.o TCPReaderWriterClient.o QnetConfigure.o $(IRCOBJS)
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread
qnrelay : QnetRelay.o
g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS)
qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread
qndvap : QnetDVAP.o DVAPDongle.o $(DSTROBJS)
g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o $(DSTROBJS) $(LDFLAGS) -pthread
qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qndvrptr : QnetDVRPTR.o $(DSTROBJS)
g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o $(DSTROBJS) $(LDFLAGS)
qnitap : QnetITAP.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qnremote : QnetRemote.o
g++ $(CPPFLAGS) -o qnremote QnetRemote.o $(LDFLAGS)
qnmodem : QnetModem.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qnvoice : QnetVoice.o
g++ $(CPPFLAGS) -o qnvoice QnetVoice.o $(LDFLAGS)
qndvap : QnetDVAP.o DVAPDongle.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS)
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread
qndvrptr : QnetDVRPTR.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS)
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qnremote : QnetRemote.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qnvoice : QnetVoice.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
%.o : %.cpp
g++ $(CPPFLAGS) -MMD -MD -c $< -o $@
@ -68,15 +82,21 @@ qnvoice : QnetVoice.o
.PHONY: clean
clean:
$(RM) $(OBJS) $(DEPS) $(PROGRAMS)
$(RM) $(OBJS) $(DEPS) $(ALL_PROGRAMS) *.gch
-include $(DEPS)
install : qngateway qnlink qnrelay
aliases : bash_aliases
/bin/cp -f bash_aliases ~/.bash_aliases
# aliases have been installed in ~/.bash_alises
# You can do 'source bash_aliases' to use them now
installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg
######### QnetGateway #########
/bin/cp -f qngateway $(BINDIR)
/bin/cp -f qnremote qnvoice $(BINDIR)
/bin/cp -f qn.cfg $(CFGDIR)
/bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR)
/bin/cp -f defaults $(CFGDIR)
/bin/cp -f system/qngateway.service $(SYSDIR)
systemctl enable qngateway.service
systemctl daemon-reload
@ -84,85 +104,70 @@ install : qngateway qnlink qnrelay
######### QnetLink #########
/bin/cp -f qnlink $(BINDIR)
/bin/cp -f announce/*.dat $(CFGDIR)
/bin/cp -f gwys.txt $(CFGDIR)
/bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR)
/bin/cp -f exec_?.sh $(CFGDIR)
/bin/cp -f system/qnlink.service $(SYSDIR)
systemctl enable qnlink.service
systemctl daemon-reload
systemctl start qnlink.service
installrelay : qnrelay
######### QnetRelay #########
/bin/cp -f qnrelay $(BINDIR)
/bin/cp -f system/qnrelay.service $(SYSDIR)
systemctl enable qnrelay.service
/bin/ln -f qnrelay $(BINDIR)/qnrelay$(MODULE)
sed -e "s/XXX/qnrelay$(MODULE)/" system/qnrelay.service > $(SYSDIR)/qnrelay$(MODULE).service
systemctl enable qnrelay$(MODULE).service
systemctl daemon-reload
systemctl start qnrelay.service
systemctl start qnrelay$(MODULE).service
installdvap : qngateway qnlink qndvap
######### QnetGateway #########
/bin/cp -f qngateway $(BINDIR)
/bin/cp -f qnremote qnvoice $(BINDIR)
/bin/cp -f qn.cfg $(CFGDIR)
/bin/cp -f system/qngateway.service $(SYSDIR)
systemctl enable qngateway.service
installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM$(MODULE).qn
######### MMDVMHost #########
/bin/ln -f $(MMPATH)/MMDVMHost $(BINDIR)/MMDVMHost$(MODULE)
/bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM$(MODULE).qn $(CFGDIR)
sed -e "s/XXX/MMDVMHost$(MODULE)/" -e "s/YYY/MMDVM$(MODULE)/" system/mmdvm.service > $(SYSDIR)/mmdvm$(MODULE).service
/bin/cp -f system/mmdvm.timer $(SYSDIR)/mmdvm$(MODULE).timer
systemctl enable mmdvm$(MODULE).timer
systemctl daemon-reload
systemctl start qngateway.service
######### QnetLink #########
/bin/cp -f qnlink $(BINDIR)
/bin/cp -f announce/*.dat $(CFGDIR)
/bin/cp -f gwys.txt $(CFGDIR)
/bin/cp -f exec_?.sh $(CFGDIR)
/bin/cp -f system/qnlink.service $(SYSDIR)
systemctl enable qnlink.service
systemctl daemon-reload
systemctl start qnlink.service
######### QnetDVAP #########
/bin/cp -f qndvap $(BINDIR)
/bin/cp -f system/qndvap.service $(SYSDIR)
systemctl enable qndvap.service
systemctl start mmdvm$(MODULE).service
installitap : qnitap
######### QnetITAP #########
/bin/ln -f qnitap $(BINDIR)/qnitap$(MODULE)
sed -e "s/XXX/qnitap$(MODULE)/" system/qnitap.service > $(SYSDIR)/qnitap$(MODULE).service
systemctl enable qnitap$(MODULE).service
systemctl daemon-reload
systemctl start qndvap.service
systemctl start qnitap$(MODULE).service
installdvrptr : qngateway qnlink qndvrptr
######### QnetGateway #########
/bin/cp -f qngateway $(BINDIR)
/bin/cp -f qnremote qnvoice $(BINDIR)
/bin/cp -f qn.cfg $(CFGDIR)
/bin/cp -f system/qngateway.service $(SYSDIR)
systemctl enable qngateway.service
installmodem : qnmodem
######### QnetModem #########
/bin/ln -f qnmodem $(BINDIR)/qnmodem$(MODULE)
sed -e "s/XXX/qnmodem$(MODULE)/" system/qnmodem.service > $(SYSDIR)/qnmodem$(MODULE).service
systemctl enable qnmodem$(MODULE).service
systemctl daemon-reload
systemctl start qngateway.service
######### QnetLink #########
/bin/cp -f qnlink $(BINDIR)
/bin/cp -f announce/*.dat $(CFGDIR)
/bin/cp -f gwys.txt $(CFGDIR)
/bin/cp -f exec_?.sh $(CFGDIR)
/bin/cp -f system/qnlink.service $(SYSDIR)
systemctl enable qnlink.service
systemctl start qnmodem$(MODULE).service
installdvap : qndvap
######### QnetDVAP #########
/bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE)
sed -e "s/XXX/qndvap$(MODULE)/" system/qndvap.service > $(SYSDIR)/qndvap$(MODULE).service
systemctl enable qndvap$(MODULE).service
systemctl daemon-reload
systemctl start qnlink.service
systemctl start qndvap$(MODULE).service
installdvrptr : qndvrptr
######### QnetDVRPTR #########
/bin/cp -f qndvrptr $(BINDIR)
/bin/cp -f system/qndvrptr.service $(SYSDIR)
systemctl enable qndvrptr.service
/bin/ln -f qndvrptr $(BINDIR)/qndvrptr$(MODULE)
sed -e "s/XXX/qndvrptr$(MODULE)/" system/qndvrptr.service > $(SYSDIR)/qndvrptr$(MODULE).service
systemctl enable qndvrptr$(MODULE).service
systemctl daemon-reload
systemctl start qndvrptr.service
systemctl start qndvrptr$(MODULE).service
installdtmf : qndtmf
/bin/cp -f qndtmf $(BINDIR)
/bin/ln -s $(shell pwd)/qndtmf $(BINDIR)
/bin/cp -f system/qndtmf.service $(SYSDIR)
systemctl enable qndtmf.service
systemctl daemon-reload
systemctl start qndtmf.service
installmmdvm :
/bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR)
/bin/cp -f $(MMPATH)/MMDVM.qn $(CFGDIR)
/bin/cp -f system/mmdvm.service $(SYSDIR)
/bin/cp -f system/mmdvm.timer $(SYSDIR)
systemctl enable mmdvm.timer
systemctl daemon-reload
systemctl start mmdvm.service
installdash :
/usr/bin/apt-get -y install python3-pip
/usr/bin/pip3 install libconf
@ -170,16 +175,7 @@ installdash :
/bin/cp -f dash/qngdash $(CRONDIR)
/bin/sh /usr/bin/python3 $(BINDIR)/qng-info.py &
uninstallmmdvm :
systemctl stop mmdvm.service
systemctl disable mmdvm.timer
/bin/rm -f $(SYSDIR)/mmdvm.service
/bin/rm -f $(SYSDIR)/mmdvm.timer
/bin/rm -f $(BINDIR)/MMDVMHost
/bin/rm -f $(CFGDIR)/MMDVM.qn
sudo systemctl daemon-reload
uninstall :
uninstallbase :
######### QnetGateway #########
systemctl stop qngateway.service
systemctl disable qngateway.service
@ -188,85 +184,65 @@ uninstall :
/bin/rm -f $(BINDIR)/qnremote
/bin/rm -f $(BINDIR)/qnvoice
/bin/rm -f $(CFGDIR)/qn.cfg
/bin/rm -f $(CFGDIR)/defaults
######### QnetLink #########
systemctl stop qnlink.service
systemctl disable qnlink.service
/bin/rm -f $(SYSDIR)/qnlink.service
/bin/rm -f $(BINDIR)/qnlink
/bin/rm -f $(CFGDIR)/already_linked.dat
/bin/rm -f $(CFGDIR)/already_unlinked.dat
/bin/rm -f $(CFGDIR)/failed_linked.dat
/bin/rm -f $(CFGDIR)/id.dat
/bin/rm -f $(CFGDIR)/linked.dat
/bin/rm -f $(CFGDIR)/unlinked.dat
/bin/rm -f $(CFGDIR)/*.dat
/bin/rm -f $(CFGDIR)/RPT_STATUS.txt
/bin/rm -f $(CFGDIR)/gwys.txt
/bin/rm -f $(CFGDIR)/exec_?.sh
uninstallrelay :
######### QnetRelay #########
systemctl stop qnrelay.service
systemctl disable qnrelay.service
/bin/rm -f $(SYSDIR)/qnrelay.service
/bin/rm -f $(BINDIR)/qnrelay
systemctl stop qnrelay$(MODULE).service
systemctl disable qnrelay$(MODULE).service
/bin/rm -f $(SYSDIR)/qnrelay$(MODULE).service
/bin/rm -f $(BINDIR)/qnrelay$(MODULE)
systemctl daemon-reload
uninstallmmdvm :
######### MMDVMHost ##########
systemctl stop mmdvm$(MODULE).service
systemctl disable mmdvm$(MODULE).timer
/bin/rm -f $(SYSDIR)/mmdvm$(MODULE).service
/bin/rm -f $(SYSDIR)/mmdvm$(MODULE).timer
/bin/rm -f $(BINDIR)/MMDVMHost$(MODULE)
/bin/rm -f $(CFGDIR)/MMDVM$(MODULE).qn
sudo systemctl daemon-reload
uninstallmodem :
######### QnetModem #########
systemctl stop qnmodem$(MODULE).service
systemctl disable qnmodem$(MODULE).service
/bin/rm -f $(SYSDIR)/qnmodem$(MODULE).service
/bin/rm -f $(BINDIR)/qnmodem$(MODULE)
systemctl daemon-reload
uninstallitap :
######### QnetITAP #########
systemctl stop qnitap$(MODULE).service
systemctl disable qnitap$(MODULE).service
/bin/rm -f $(SYSDIR)/qnitap$(MODULE).service
/bin/rm -f $(BINDIR)/qnitap$(MODULE)
systemctl daemon-reload
uninstalldvap :
######### QnetGateway #########
systemctl stop qngateway.service
systemctl disable qngateway.service
/bin/rm -f $(SYSDIR)/qngateway.service
/bin/rm -f $(BINDIR)/qngateway
/bin/rm -f $(BINDIR)/qnremote
/bin/rm -f $(BINDIR)/qnvoice
/bin/rm -f $(CFGDIR)/qn.cfg
######### QnetLink #########
systemctl stop qnlink.service
systemctl disable qnlink.service
/bin/rm -f $(SYSDIR)/qnlink.service
/bin/rm -f $(BINDIR)/qnlink
/bin/rm -f $(CFGDIR)/already_linked.dat
/bin/rm -f $(CFGDIR)/already_unlinked.dat
/bin/rm -f $(CFGDIR)/failed_linked.dat
/bin/rm -f $(CFGDIR)/id.dat
/bin/rm -f $(CFGDIR)/linked.dat
/bin/rm -f $(CFGDIR)/unlinked.dat
/bin/rm -f $(CFGDIR)/RPT_STATUS.txt
/bin/rm -f $(CFGDIR)/gwys.txt
/bin/rm -f $(CFGDIR)/exec_?.sh
######### QnetDVAP #########
systemctl stop qndvap.service
systemctl disable qndvap.service
/bin/rm -f $(SYSDIR)/qndvap.service
/bin/rm -f $(BINDIR)/qndvap
systemctl stop qndvap$(MODULE).service
systemctl disable qndvap$(MODULE).service
/bin/rm -f $(SYSDIR)/qndvap$(MODULE).service
/bin/rm -f $(BINDIR)/qndvap$(MODULE)
systemctl daemon-reload
uninstalldvrptr :
######### QnetGateway #########
systemctl stop qngateway.service
systemctl disable qngateway.service
/bin/rm -f $(SYSDIR)/qngateway.service
/bin/rm -f $(BINDIR)/qngateway
/bin/rm -f $(BINDIR)/qnremote
/bin/rm -f $(BINDIR)/qnvoice
/bin/rm -f $(CFGDIR)/qn.cfg
######### QnetLink #########
systemctl stop qnlink.service
systemctl disable qnlink.service
/bin/rm -f $(SYSDIR)/qnlink.service
/bin/rm -f $(BINDIR)/qnlink
/bin/rm -f $(CFGDIR)/already_linked.dat
/bin/rm -f $(CFGDIR)/already_unlinked.dat
/bin/rm -f $(CFGDIR)/failed_linked.dat
/bin/rm -f $(CFGDIR)/id.dat
/bin/rm -f $(CFGDIR)/linked.dat
/bin/rm -f $(CFGDIR)/unlinked.dat
/bin/rm -f $(CFGDIR)/RPT_STATUS.txt
/bin/rm -f $(CFGDIR)/gwys.txt
/bin/rm -f $(CFGDIR)/exec_?.sh
######### QnetDVRPTR #########
systemctl stop qndvrptr.service
systemctl disable qndvrptr.service
/bin/rm -f $(SYSDIR)/qndvrptr.service
/bin/rm -f $(BINDIR)/qndvrptr
systemctl stop qndvrptr$(MODULE).service
systemctl disable qndvrptr$(MODULE).service
/bin/rm -f $(SYSDIR)/qndvrptr$(MODULE).service
/bin/rm -f $(BINDIR)/qndvrptr$(MODULE)
systemctl daemon-reload
uninstalldtmf :

@ -0,0 +1,292 @@
/*
* Copyright (C) 2010, 2011, 2012 by Scott Lawson KI4LKF
* addition Copyright (C) 2018 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
I have substantially modified (and hopefully simplified) the configuration
of this software, now, modules QnetGateway, QnetLink, QnetDVAP and QnetDVRPTR
all use a single configuration file. Further, and this is the exciting
part, NEARLY ALL of the parameters these modules need have a useful
default value. If the default value is acceptible to you (and it
probably will be) then you only need to specify what you need to change.
This means that for most users, you will only need to specify a few
parameters. In addtion, there is a new script, qnconfig to help you easily
build your configuration file, qn.cfg.
Some other features are discussed below and are pretty much directly
quoted from KI4LKF original documentation.
Tom Early, n7tae (at) arrl (dot) net
G2_ircDDB adapted from the OpenG2
DESCRIPTION
=================
QnetGateway supports the following commands in YRCALL
Note: In the commands that folow, _ is a SPACE.
1)
For Echotest/playback.
YRCALL=_ _ _ _ _ _ _E
2)
For Voice Mail:
YRCALL=_ _ _ _ _ _ S0
The above command will Store/create voice mail in a proprietary file
x_voicemail.dat2.
YRCALL=_ _ _ _ _ _ R0
The above command will Recall/playback voice mail from the recorded file
x_voicemail.dat2.
YRCALL=_ _ _ _ _ _ C0
The above command will Clear/delete voice mail. File x_voicemail.dat2 will be
deleted. In all cases, the letter x in the file name x_voicemail is the
module A,B or C.
3)
For inquiring the status of the link:
YRCALL=_ _ _ _ _ _ _I
4)
For unlinking:
YRCALL=_ _ _ _ _ _ _U
5)
For linking:
YRCALL=XXNYYYML
Where XXNYYY is a friendly gateway, M is the gateways's module and L is the
LINK command.
YRCALL=XRFNNNML
Where XRFNNN is a friendly reflector, M is the reflector's module and L is
the LINK command.
By default, if the target is unavailable or becomes unavialable, QnetLink
will attempt to re-link approximately every 50 seconds, at least until
an unlinking command is sent. You will hear status of the connection attempt
about every 50 seconds. If the target sends an unlink request, QnetLink will
honor the request and not attempt to relink until another linking command is
given. This automatic re-linking can be disabled with:
module_x_auto_link=false
in your configuration file, qn.cfg. Here the "x" is the module, "a", "b"
or "c". This variable can be set in the qnconfig if executing in
expert mode:
./qnconfig expert
Note about linking:
After linking succeeds, set YRCALL=CQCQCQ because your audio will go to the
remote reflector ONLY if YRCALL=CQCQCQ.
6)
For executing scripts:
YRCALL=_ _ _ _ _ _ nX
where n can be from 0-9 or A-Z.
Example: YRCALL=_ _ _ _ _ _1X
Then the script exec_1.sh will be executed.
Two scripts, exec_R.sh and exec_H.sh are included to reboot and halt your
system, respectively. Also note that rpt1 is passed to these scripts\
so you can use this as an input parameter for your scripts.
Only admins can execute scripts, so set QnetLink.admin to your callsign
Required software to run QnetGateway correctly:
--- QnetGateway: The G2 audio gateway. This handle routing using an IRCddb
network and also handle echo and voicemail and some audio
notifications.
--- QnetLink: This communicates with QnetGateway to link the local G2 gateway
to reflectors.
--- QnetDVAP, QnetDVRPTR, QNetITAP (can only be used with a compatible
Icom radio that supports Terminal mode) or QnetRelay (used with
MMDVMHost-compatible modems like the ZUMspot or DVMega).
ROUTING methods
===============
Some Dstar routing examples follow. Please do not use the same data because
KJ4NHF is one of our own Dstar repeaters, and KI4LKF is a personal callsign
in our group.
Example of ZONE routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to reach remote gateway XXNYYY module C
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=/XXNYYYC
RPT1=KJ4NHF B
RPT2=KJ4NHF G
Example of CALLSIGN routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to talk to user XX0XXX
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=XX0XXX
RPT1=KJ4NHF B
RPT2=KJ4NHF G
Example of GROUP routing:
Lets say you want to connect to the DSTAR1 group from your
local repeater module:
MYCALL=KI4LKF
YRCALL=DSTAR1
RPT1=KJ4NHF B
RPT2=KJ4NHF G
Example of Cross-Band routing:
Lets say that your repeater is KJ4NHF, and you are currently on
your local repeater module B, your callsign is KI4LKF
and you want to talk from your local module B to your local module C
In this case, your radio should be programmed like this:
MYCALL=KI4LKF
YRCALL=CQCQCQ
RPT1=KJ4NHF B
RPT2=KJ4NHF C
DTMF decoding and processing
=============================
Prepare the software to decode and process DTMF tones
-----------------------------------------------------
Copy the Shell script qndtmf.sh to qndtmf and make any
changes/additions/subtractions to the script then install your
DTMF script with "sudo make installdtmf"
Note: When local RF user has entered dtmf tones on the Dstar HT
and then PTT is released, QnetGateway will print the whole
sequence in the QnetGateway.log just before it creates the
dtmf file under /tmp.
How to enter DTMF tones correctly on your Dstar HT
What dtmf tones are being decoded and processed
-----------------------------------------------
To link to an XRF reflector: Example: B02102
That says: link to XRF021 module B. So, we use the # dtmf key
to mean that we're interested in linking to an XRF reflector. The
last two digits are the remote module 01 thru 05 (which is
translated to A thru E).
To link to a DCS reflector: Example: D00126
That says: link to DCS001 module Z. So, we use the D dtmf key
to mean that we're interested in linking to a DCS reflector. The
last two digits are the remote module 01 thru 26 (which is
translated to A thru Z).
To link to REF: Example: *01601
That says: link to REF016 module A. So, we use the * dtmf key
to mean that we're interested in linking to a REF reflector. The
last two digits are the remote module 01 thru 05 (which is
translated to A thru E).
To unlink (from any reflector xrf or ref): #
To get the "link status": 0 or 00
Note:
You can extend your dtmf shell script to do more things. like force
your repeater to ID itself. Any YRCALL command that can be executed
by g2link_test, can be added to the shell script. Basically, the
Linux shell script proc_QnetGateway_dtmfs.sh converts the decoded
dtmf tones into YRCALL commands using g2link_test program.
===========
QnetLink is a small program that is used to link a local RF repeater
band to a remote reflector. QnetLink software is used by our QnetGateway
(an IRCddb gateway).
Before we begin, there are some dat files included that are the voice
prompts used by QnetGateway and QnetLink:
rebooting.dat
baddtmfcmd.dat
gatewayrestart.dat
shutdown.dat
connected2network.dat
notincache.dat
gatewaynotfound.dat
already_linked.dat
already_unlinked.dat
failed_link.dat
linked.dat
unlinked.dat
id.dat
All of the above dat files are special AMBE format files and they are used
when certain events take place. For example, when the link between your
gateway and a reflector is established, then the file "linked.dat" will
be played over RF, so anyone listening on your repeater, will hear the
announcement that the link has been established. You do not have to
change these files, unless you want to have your own personal voice
played over RF. These dat files were created using a computer and they
are NOT anyone's voice.
The only file most hams will need to change is id.dat. When the gateway
is not linked and the RF user sets YRCALL=_______I to request the
status of the link, the file id.dat will be played back over RF. But
you can create your own id.dat file that should identify your own
repeater with extra information if you like.
For example, you could create your own dat audio file that contains
the audio: "This is repeater ...". A simple way to create your own
recorded "repeater identification file" id.dat is to use your Dstar HT
and set YRCALL command: YRCALL=______S0 and key up your repeater.
Start talking, and the gateway will record your audio into the file
x_voicemail.dat2 where x is one of A.B or C. Now copy that file:
sudo tail -c +56 /tmp/C_voicemail.dat2 > /usr/local/etc/id.dat
See the DTMF+REMOTE+VOICE.README for more information.
============
You may be thinking why did we put all this source code inside QnetLink
instead of putting it all inside the Gateway software QnetGateway.
Having divided the functionality produces a smaller memory and resource
requirement for your computer.
QnetDVAP
The serial number required in the .cfg file is obtained from the label
that can be seen on the DVAP circuit board. In most cases set the power
to maximum level.
QnetDVRPTR
The serial number required in the qn.cfg file can most easily be obtained
by examining the log file once the board has been powered up. Once you
know the board serial number, edit g2.cfg. After editing g2.cfg file you
can restart the effected program with "sudo systemctl restart qndvrptr".
Or you can always just reboot with "sudo reboot" or "sudo shutdown -r now".
Rig specific parameters are in "module.x.rf_rx_level", "module.x.inverse.rx" and
"module.x.inverse.tx". You need to play with these to work best with your rig.
With my Kenwood TM-V71, I use the default values.
You can first start with inverse.rx, trying true or false. Use the echo command
on your radio and look in the logs to see if you are getting into the
dvprtr. Remember to restart the QnetDVRPTR service anytime you modify
/usr/local/etc/g2.cfg. Once you are able to get into the QnetDVRPTR,
then you can work out the inverse.tx paramter. Once you hear anything
with in the echo mode, move rf_rx_level up or down to get the best
audio.
Once you have a working system, it's a good idea to back up you qn.cfg
files.

@ -0,0 +1,252 @@
/*
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cstdio>
#include <cstring>
#include "QnetConfigure.h"
CQnetConfigure::CQnetConfigure()
{
}
CQnetConfigure::~CQnetConfigure()
{
defaults.empty();
cfg.empty();
}
char *CQnetConfigure::Trim(char *s)
{
size_t len = strlen(s);
while (len && isspace(s[len-1]))
s[--len] = '\0';
while (*s && len && isspace(*s))
len = strlen(++s);
return s;
}
bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map<std::string, std::string> &amap)
{
FILE *fp = fopen(configfile, "r");
if (fp) {
char line[2048];
while (fgets(line, 2048, fp)) {
char *key = strtok(line, "=");
key = Trim(key);
if (strlen(key) && '#' != *key) {
char *val = strtok(NULL, "\r\n");
char *val2 = Trim(val);
if ('\'' == val2[0]) {
if ('\'' == val2[1])
val[0] = '\0';
else
val = strtok(val2, "'");
} else
val = strtok(val2, "# \t");
amap[key] = val;
}
}
fclose(fp);
return false;
}
fprintf(stderr, "could not open file %s\n", configfile);
return true;
}
bool CQnetConfigure::Initialize(const char *file)
{
std::string filename(CFG_DIR);
filename.append("/defaults");
if (ReadConfigFile(filename.c_str(), defaults))
return true;
return ReadConfigFile(file, cfg);
}
bool CQnetConfigure::KeyExists(const std::string &key)
{
return (cfg.end() != cfg.find(key));
}
bool CQnetConfigure::GetDefaultBool(const std::string &path, const std::string &mod, bool &dvalue)
{
std::string value;
if (GetDefaultString(path, mod, value))
return true; // No default value defined!
if ('0'==value.at(0) || 'f'==value.at(0) || 'F'==value.at(0))
dvalue = false;
else if ('1'==value.at(0) || 't'==value.at(0) || 'T'==value.at(0))
dvalue = true;
else {
fprintf(stderr, "%s=%s doesn't seem to be a boolean!\n", path.c_str(), value.c_str());
return true;
}
return false;
}
bool CQnetConfigure::GetDefaultDouble(const std::string &path, const std::string &mod, double &dvalue)
{
std::string value;
if (GetDefaultString(path, mod, value))
return true; // No default value defined!
dvalue = std::stod(value);
return false;
}
bool CQnetConfigure::GetDefaultInt(const std::string &path, const std::string &mod, int &dvalue)
{
std::string value;
if (GetDefaultString(path, mod, value))
return true; // No default value defined!
dvalue = std::stoi(value);
return false;
}
bool CQnetConfigure::GetDefaultString(const std::string &path, const std::string &mod, std::string &dvalue)
{
std::string search, search_again;
if (mod.empty()) {
search = path + "_d"; // there is no mod, so this is a simple search
} else {
search_again = mod; // we're looking from a module value. We may have to look for non-generic module parameters
if (0==path.compare(0, 7, "module_") && ('a'==path.at(7) || 'b'==path.at(7) || 'c'==path.at(7)) && '_'==path.at(8)) {
// path begins with module_{a|b|c}_
if (0==mod.compare("dvrptr") || 0==mod.compare("dvap") || 0==mod.compare("mmdvmhost") || 0==mod.compare("mmdvmmodem") || 0==mod.compare("itap")) {
// and the module is recognized
search = path;
search.replace(7, 1, 1, 'x');
search_again += path.substr(8); // now the search_again path might look like dvap_frequency, for example.
} else {
fprintf(stderr, "Unrecognized module type = '%s'\n", mod.c_str());
return true;
}
} else {
fprintf(stderr, "%s looks like an ilformed request from module '%s'\n", path.c_str(), mod.c_str());
return true;
}
}
auto it = defaults.find(search);
if (defaults.end() == it) {
it = defaults.find(search_again);
if (defaults.end() == it)
return true;
}
dvalue = it->second;
return false;
}
bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, bool &value)
{
auto it = cfg.find(path);
if (cfg.end() == it) {
bool dvalue;
if (GetDefaultBool(path, mod, dvalue)) {
fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str());
return true;
}
value = dvalue; // found a value in the defaults
} else { // found a value in the cfg file
char c = it->second.at(0);
if ('0'==c || 'f'==c || 'F'==c)
value = false;
else if ('1'==c || 't'==c || 'T'==c)
value = true;
else {
fprintf(stderr, "%s=%s doesn't seem to define a boolean\n", path.c_str(), it->second.c_str());
return true;
}
}
printf("%s = %s\n", path.c_str(), value ? "true" : "false");
return false;
}
bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max)
{
auto it = cfg.find(path);
if (cfg.end() == it) {
double dvalue;
if (GetDefaultDouble(path, mod, dvalue)) {
fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str());
return true;
}
if (dvalue < min || dvalue > max) {
fprintf(stderr, "Default value %s=%g is out of acceptable range\n", path.c_str(), value);
return true;
}
value = dvalue;
} else {
value = std::stod(it->second);
if (value < min || value > max) {
fprintf(stderr, "%s=%g is out of acceptable range\n", path.c_str(), value);
return true;
}
}
printf("%s = %g\n", path.c_str(), value);
return false;
}
bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max)
{
auto it = cfg.find(path);
if (cfg.end() == it) {
int dvalue;
if (GetDefaultInt(path, mod, dvalue)) {
fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str());
return true;
}
if (dvalue < min || dvalue > max) {
fprintf(stderr, "Default value %s=%d is out of acceptable range\n", path.c_str(), value);
return true;
}
value = dvalue;
} else {
value = std::stoi(it->second);
if (value < min || value > max) {
fprintf(stderr, "%s=%s is out of acceptable range\n", path.c_str(), it->second.c_str());
return true;
}
}
printf("%s = %d\n", path.c_str(), value);
return false;
}
bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, std::string &value, int min, int max)
{
auto it = cfg.find(path);
if (cfg.end() == it) {
std::string dvalue;
if (GetDefaultString(path, mod, dvalue)) {
fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str());
return true;
}
int l = dvalue.length();
if (min-1>=l || l>max) {
printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str());
return true;
}
value.assign(dvalue);
} else {
value.assign(it->second);
int l = value.length();
if (l<min || l>max) {
printf("%s='%s' is wrong size\n", path.c_str(), value.c_str());
return true;
}
}
printf("%s = '%s'\n", path.c_str(), value.c_str());
return false;
}

@ -0,0 +1,45 @@
/*
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <string>
#include <map>
class CQnetConfigure {
public:
CQnetConfigure();
virtual ~CQnetConfigure();
bool Initialize(const char *configfile);
bool GetValue(const std::string &path, const std::string &mod, bool &value);
bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max);
bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max);
bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max);
bool KeyExists(const std::string &key);
private:
std::map<std::string, std::string> defaults;
std::map<std::string, std::string> cfg;
char *Trim(char *s);
bool ReadConfigFile(const char *file, std::map<std::string, std::string> &amap);
bool GetDefaultBool (const std::string &key, const std::string &mod, bool &dval);
bool GetDefaultDouble(const std::string &key, const std::string &mod, double &dval);
bool GetDefaultInt (const std::string &key, const std::string &mod, int &dval);
bool GetDefaultString(const std::string &key, const std::string &mod, std::string &dval);
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 by Thomas Early N7TAE
* Copyright (C) 2018-2019 by Thomas Early N7TAE
*
* 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
@ -16,40 +16,33 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <libconfig.h++>
#include <map>
#include <set>
#include "QnetTypeDefs.h"
#include "SEcho.h"
#include "UnixDgramSocket.h"
#include "aprs.h"
#include "SockAddress.h"
using namespace libconfig;
#define IP_SIZE 15
#define MAXHOSTNAMELEN 64
#define CALL_SIZE 8
#define MAX_DTMF_BUF 32
typedef struct echo_tag {
time_t last_time;
unsigned short streamid;
int fd;
char file[FILENAME_MAX + 1];
} SECHO;
typedef struct to_remote_g2_tag {
unsigned short streamid;
struct sockaddr_in toDst4;
CSockAddress toDstar;
time_t last_time;
} STOREMOTEG2;
typedef struct torepeater_tag {
// help with header re-generation
unsigned char saved_hdr[58]; // repeater format
uint32_t saved_adr;
SDSVT saved_hdr; // repeater format
CSockAddress saved_addr;
unsigned short streamid;
uint32_t adr;
struct sockaddr_in band_addr;
CSockAddress addr;
time_t last_time;
std::atomic<unsigned short> G2_COUNTER;
unsigned char sequence;
} STOREPEATER;
@ -64,7 +57,7 @@ typedef struct band_txt_tag {
time_t last_time;
char txt[64]; // Only 20 are used
unsigned short txt_cnt;
bool txt_stats_sent;
bool sent_key_on_msg;
char dest_rptr[CALL_SIZE + 1];
@ -85,16 +78,39 @@ class CQnetGateway {
public:
CQnetGateway();
~CQnetGateway();
void Process();
bool Init(char *cfgfile);
private:
SPORTIP g2_internal, g2_external, g2_link, ircddb;
// link type
int link_family[3] = { AF_UNSPEC, AF_UNSPEC, AF_UNSPEC };
// network type
int af_family[2] = { AF_UNSPEC, AF_UNSPEC };
// text stuff
bool new_group[3] = { true, true, true };
unsigned char header_type = 0;
short to_print[3] = { 0, 0, 0 };
bool ABC_grp[3] = { false, false, false };
bool C_seen[3] = { false, false, false };
int Index[3] = { -1, -1, -1 };
SPORTIP g2_external, g2_ipv6_external, ircddb[2];
CUnixDgramReader Link2Gate, Modem2Gate;
CUnixDgramWriter Gate2Link, Gate2Modem[3];
std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass;
std::string gate2link, link2gate, gate2modem[3], modem2gate;
bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs;
std::string OWNER, owner, FILE_STATUS, FILE_DTMF, FILE_ECHOTEST, IRCDDB_PASSWORD[2], FILE_QNVOICE_FILE;
int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit;
bool GATEWAY_SEND_QRGS_MAP, GATEWAY_HEADER_REGEN, APRS_ENABLE, playNotInCache;
bool LOG_DEBUG, LOG_IRC, LOG_DTMF, LOG_QSO;
std::map <uint32_t, uint16_t> portmap;
int TIMING_PLAY_WAIT, TIMING_PLAY_DELAY, TIMING_TIMEOUT_ECHO, TIMING_TIMEOUT_VOICEMAIL, TIMING_TIMEOUT_REMOTE_G2, TIMING_TIMEOUT_LOCAL_RPTR, dtmf_digit;
unsigned int vPacketCount[3] = { 0, 0, 0 };
std::set<std::string> findRoute;
// data needed for aprs login and aprs beacon
// RPTR defined in aprs.h
@ -109,25 +125,20 @@ private:
STOREMOTEG2 to_remote_g2[3]; // 0=A, 1=B, 2=C
// input from remote G2 gateway
int g2_sock = -1;
struct sockaddr_in fromDst4;
int g2_sock[2] = { -1, -1 };
CSockAddress fromDstar;
// Incoming data from remote systems
// must be fed into our local repeater modules.
STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C
// input from our own local repeater modules
int srv_sock = -1;
SDSTR rptrbuf; // 58 or 29 or 32, max is 58
struct sockaddr_in fromRptr;
SDSTR end_of_audio;
SDSVT end_of_audio;
// send packets to g2_link
struct sockaddr_in plug;
// for talking with the irc server
CIRCDDB *ii;
CIRCDDB *ii[2];
// for handling APRS stuff
CAPRS *aprs;
@ -135,30 +146,42 @@ private:
SBANDTXT band_txt[3]; // 0=A, 1=B, 2=C
/* Used to validate MYCALL input */
regex_t preg;
std::regex preg;
// CACHE used to cache users, repeaters,
// gateways, IP numbers coming from the irc server
std::map<std::string, std::string> user2rptr_map, rptr2gwy_map, gwy2ip_map;
std::map<std::string, std::string> user2rptr_map[2], rptr2gwy_map[2], gwy2ip_map[2];
pthread_mutex_t irc_data_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t irc_data_mutex[2] = PTHREAD_MUTEX_INITIALIZER;
int open_port(const SPORTIP &pip);
// dtmf stuff
int dtmf_buf_count[3];
char dtmf_buf[3][MAX_DTMF_BUF + 1];
int dtmf_last_frame[3];
unsigned int dtmf_counter[3];
bool VoicePacketIsSync(const unsigned char *text);
void AddFDSet(int &max, int newfd, fd_set *set);
int open_port(const SPORTIP *pip, int family);
void calcPFCS(unsigned char *packet, int len);
void GetIRCDataThread();
int get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU);
bool get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU);
void PlayFileThread(char *file);
void GetIRCDataThread(const int i);
int get_yrcall_rptr_from_cache(const int i, const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU);
int get_yrcall_rptr(const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU);
void PlayFileThread(SECHO &edata);
void compute_aprs_hash();
void APRSBeaconThread();
void ProcessTimeouts();
void ProcessSlowData(unsigned char *data, unsigned short sid);
void ProcessG2(const ssize_t g2buflen, const SDSVT &g2buf, const int sock_source);
void ProcessModem();
bool Flag_is_ok(unsigned char flag);
void UnpackCallsigns(const std::string &str, std::set<std::string> &set, const std::string &delimiters = ",");
void PrintCallsigns(const std::string &key, const std::set<std::string> &set);
int FindIndex(const int i) const;
// read configuration file
bool read_config(char *);
bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value);
bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value);
bool get_value(const Config &cfg, const char *path, bool &value, bool default_value);
bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value);
bool ReadConfig(char *);
/* aprs functions, borrowed from my retired IRLP node 4201 */
void gps_send(short int rptr_idx);
@ -169,8 +192,4 @@ private:
void set_dest_rptr(int mod_ndx, char *dest_rptr);
bool validate_csum(SBANDTXT &bt, bool is_gps);
public:
void process();
int init(char *cfgfile);
};

@ -0,0 +1,722 @@
/*
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* CQnetITAP::GetITAPData() is based on some code that is...
* Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <exception>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <csignal>
#include <ctime>
#include <cstdlib>
#include <netdb.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <termios.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <thread>
#include <chrono>
#include "QnetITAP.h"
#include "QnetTypeDefs.h"
#include "QnetConfigure.h"
#include "Timer.h"
#define ITAP_VERSION "QnetITAP-2.1.5"
std::atomic<bool> CQnetITAP::keep_running(true);
CQnetITAP::CQnetITAP(int mod)
: assigned_module(mod)
{
}
CQnetITAP::~CQnetITAP()
{
}
bool CQnetITAP::Initialize(const char *cfgfile)
{
if (ReadConfig(cfgfile))
return true;
struct sigaction act;
act.sa_handler = &CQnetITAP::SignalCatch;
sigemptyset(&act.sa_mask);
if (sigaction(SIGTERM, &act, 0) != 0) {
printf("sigaction-TERM failed, error=%d\n", errno);
return true;
}
if (sigaction(SIGHUP, &act, 0) != 0) {
printf("sigaction-HUP failed, error=%d\n", errno);
return true;
}
if (sigaction(SIGINT, &act, 0) != 0) {
printf("sigaction-INT failed, error=%d\n", errno);
return true;
}
Modem2Gate.SetUp(modem2gate.c_str());
if (Gate2Modem.Open(gate2modem.c_str()))
return true;
return false;
}
int CQnetITAP::OpenITAP()
{
int fd = open(ITAP_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0);
if (fd < 0) {
printf("Failed to open device [%s], error=%d, message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno));
return -1;
}
if (isatty(fd) == 0) {
printf("Device %s is not a tty device\n", ITAP_DEVICE.c_str());
close(fd);
return -1;
}
static termios t;
if (tcgetattr(fd, &t) < 0) {
printf("tcgetattr failed for %s, error=%d, message-%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno));
close(fd);
return -1;
}
t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG);
t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY);
t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
t.c_cflag |= CS8;
t.c_oflag &= ~(OPOST);
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 10;
cfsetospeed(&t, B38400);
cfsetispeed(&t, B38400);
if (tcsetattr(fd, TCSANOW, &t) < 0) {
printf("tcsetattr failed for %s, error=%dm message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno));
close(fd);
return -1;
}
return fd;
}
void CQnetITAP::DumpSerialPacket(const char *title, const unsigned char *buf)
{
printf("%s: ", title);
if (buf[0] > 41) {
printf("UNKNOWN: length=%u", (unsigned)buf[0]);
return;
}
SITAP itap;
memcpy(&itap, buf, buf[0]);
switch (itap.type) {
case 0x03U: //pong
printf("Pong\n");
break;
case 0x10U: // header
case 0x20U:
printf("Header ur=%8.8s r1=%8.8s r2=%8.8s my=%8.8s/%4.4s", itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm);
break;
case 0x12U: // data
case 0x22U:
printf("Data count=%u seq=%u f=%02u%02u%02u a=%02u%02u%02u%02u%02u%02u%02u%02u%02u t=%02u%02u%02u\n", itap.voice.counter, itap.voice.sequence, itap.header.flag[0], itap.header.flag[1], itap.header.flag[2], itap.voice.ambe[0], itap.voice.ambe[1], itap.voice.ambe[2], itap.voice.ambe[3], itap.voice.ambe[4], itap.voice.ambe[5], itap.voice.ambe[6], itap.voice.ambe[7], itap.voice.ambe[8], itap.voice.text[0], itap.voice.text[1], itap.voice.text[2]);
break;
case 0x11U: // header acknowledgement
case 0x21U:
printf("Header acknowledgement code=%02u", itap.header.flag[0]);
break;
case 0x13U: // data acknowledgment
case 0x23U:
printf("Data acknowledgement seq=%02u code=%02u", itap.header.flag[0], itap.header.flag[1]);
break;
default:
printf("UNKNOWN packet buf[0] = 0x%02u\n", buf[0]);
break;
}
}
REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf)
{
// Some methods adapted from Jonathan G4KLX's CIcomController::GetResponse()
// and CSerialController::read()
// Get the buffer size or nothing at all
int ret = read(serfd, buf, 1U);
if (ret < 0) {
printf("Error when reading first byte from the Icom radio %d: %s", errno, strerror(errno));
return RT_ERROR;
}
if (ret == 0)
return RT_TIMEOUT;
if (buf[0U] == 0xFFU)
return RT_TIMEOUT;
unsigned int length = buf[0U];
if (length >= 100U) {
printf("Invalid data received from the Icom radio, length=%d\n", length);
return RT_ERROR;
}
unsigned int offset = 1U;
while (offset < length) {
ret = read(serfd, buf + offset, length - offset);
if (ret<0 && errno!=EAGAIN) {
printf("Error when reading buffer from the Icom radio %d: %s\n", errno, strerror(errno));
return RT_ERROR;
}
if (ret > 0)
offset += ret;
}
switch (buf[1U]) {
case 0x03U:
return RT_PONG;
case 0x10U:
return RT_HEADER;
case 0x12U:
return RT_DATA;
case 0x21U:
return RT_HEADER_ACK;
case 0x23U:
return RT_DATA_ACK;
default:
return RT_UNKNOWN;
}
}
void CQnetITAP::Run(const char *cfgfile)
{
if (Initialize(cfgfile))
return;
serfd = OpenITAP();
if (serfd < 0)
return;
int ug2m = Gate2Modem.GetFD();
printf("gate2modem=%d, serial=%d\n", ug2m, serfd);
keep_running = true;
unsigned int poll_counter = 0;
bool is_alive = false;
acknowledged = true;
CTimer ackTimer;
CTimer lastdataTimer;
CTimer pingTimer;
double pingtime = 0.001;
while (keep_running) {
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(serfd, &readfds);
FD_SET(ug2m, &readfds);
int maxfs = (serfd > ug2m) ? serfd : ug2m;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 5000;
// don't care about writefds and exceptfds:
// and we'll wait for 5 ms
int ret = select(maxfs+1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno));
break;
}
// check for a dead or disconnected radio
if (10.0 < lastdataTimer.time()) {
printf("no activity from radio for 10 sec. Exiting...\n");
break;
}
if (pingTimer.time() >= pingtime) {
if (poll_counter < 18 ) {
const unsigned char poll[2] = { 0xffu, 0xffu };
SendTo(poll);
if (poll_counter == 17)
pingtime = 1.0;
} else {
const unsigned char ping[2] = { 0x02u, 0x02u };
SendTo(ping);
}
poll_counter++;
pingTimer.start();
}
unsigned char buf[100];
if (keep_running && FD_ISSET(serfd, &readfds)) { // there is something to read!
switch (GetITAPData(buf)) {
case RT_ERROR:
keep_running = false;
break;
case RT_HEADER:
{
unsigned char ack_header[3] = { 0x03U, 0x11U, 0x0U };
SendTo(ack_header);
}
if (ProcessITAP(buf))
keep_running = false;
lastdataTimer.start();
break;
case RT_DATA:
{
unsigned char ack_voice[4] = { 0x04U, 0x13U, 0x0U, 0x0U };
ack_voice[2] = buf[2];
SendTo(ack_voice);
}
if (ProcessITAP(buf))
keep_running = false;
lastdataTimer.start();
break;
case RT_PONG:
if (! is_alive) {
if (LOG_DEBUG) {
auto count = queue.size();
if (count)
printf("%u packets in queue. Icom radio is connected.", (unsigned int)count);
} else
printf("Icom Radio is connected.\n");
is_alive = true;
}
lastdataTimer.start();
break;
case RT_HEADER_ACK:
if (acknowledged) {
fprintf(stderr, "ERROR: Header already acknowledged!\n");
} else {
if (0x0U == buf[2])
acknowledged = true;
}
lastdataTimer.start();
break;
case RT_DATA_ACK:
if (acknowledged) {
fprintf(stderr, "ERROR: voice frame %d already acknowledged!\n", (int)buf[2]);
} else {
if (0x0U == buf[3])
acknowledged = true;
}
lastdataTimer.start();
break;
case RT_TIMEOUT: // nothing or 0xff
break;
default:
if (buf[0] != 255)
DumpSerialPacket("GetITAPData returned", buf);
break;
}
FD_CLR(serfd, &readfds);
}
if (keep_running && FD_ISSET(ug2m, &readfds)) {
ssize_t len = Gate2Modem.Read(buf, 100);
if (len < 0) {
printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno));
break;
}
if (0 == memcmp(buf, "DSVT", 4)) {
//printf("read %d bytes from QnetGateway\n", (int)len);
if (ProcessGateway(len, buf))
break;
}
FD_CLR(ug2m, &readfds);
}
// send queued frames
if (keep_running) {
if (acknowledged) {
if (is_alive) {
if (! queue.empty()) {
CFrame frame = queue.front();
queue.pop();
SendTo(frame.data());
ackTimer.start();
acknowledged = false;
}
}
} else { // we are waiting on an acknowledgement
if (ackTimer.time() >= 0.06) {
if (LOG_DEBUG)
fprintf(stderr, "Serial port communication error, restarting...\n");
close(serfd);
poll_counter = 0;
pingtime = 0.001;
is_alive = false;
acknowledged = true;
lastdataTimer.start();
pingTimer.start();
serfd = OpenITAP();
if (serfd < 0) {
keep_running = false;
} else {
while (! queue.empty())
queue.pop();
}
}
}
}
}
close(serfd);
Gate2Modem.Close();
}
int CQnetITAP::SendTo(const unsigned char *buf)
{
ssize_t n;
unsigned int ptr = 0;
unsigned int length = (0xffu == buf[0]) ? 2 : buf[0];
while (ptr < length) {
n = write(serfd, buf + ptr, length - ptr);
if (n < 0) {
if (EAGAIN != errno) {
printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno));
return -1;
}
}
ptr += n;
}
n = 0; // send an ending 0xffu
while (0 == n) {
const unsigned char push = 0xffu;
n = write(serfd, &push, 1);
if (n < 0) {
if (EAGAIN != errno) {
printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno));
return -1;
}
}
}
return length;
}
bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw)
{
static unsigned char counter = 0;
if (27==len || 56==len) { //here is dstar data
SDSVT dsvt;
memcpy(dsvt.title, raw, len); // transfer raw data to SDSVT struct
SITAP itap; // destination
if (56 == len) { // write a Header packet
counter = 0;
itap.length = 41U;
itap.type = 0x20;
memcpy(itap.header.flag, dsvt.hdr.flag, 3);
if (RPTR_MOD == dsvt.hdr.rpt2[7]) {
memcpy(itap.header.r1, dsvt.hdr.rpt2, 8);
memcpy(itap.header.r2, dsvt.hdr.rpt1, 8);
} else {
memcpy(itap.header.r1, dsvt.hdr.rpt1, 8);
memcpy(itap.header.r2, dsvt.hdr.rpt2, 8);
}
memcpy(itap.header.ur, dsvt.hdr.urcall, 8);
memcpy(itap.header.my, dsvt.hdr.mycall, 8);
memcpy(itap.header.nm, dsvt.hdr.sfx, 4);
if (LOG_QSO)
printf("Queued ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm);
} else { // write an AMBE packet
itap.length = 16U;
itap.type = 0x22U;
itap.voice.counter = counter++;
itap.voice.sequence = dsvt.ctrl;
if (LOG_QSO && (dsvt.ctrl & 0x40))
printf("Queued ITAP end of stream\n");
if ((dsvt.ctrl & ~0x40U) > 20)
printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence);
memcpy(itap.voice.ambe, dsvt.vasd.voice, 12);
}
queue.push(CFrame(&itap.length));
} else
printf("DEBUG: ProcessGateway: unusual packet size read len=%d\n", len);
return false;
}
bool CQnetITAP::ProcessITAP(const unsigned char *buf)
{
static short stream_id = 0U;
SITAP itap;
unsigned int len = (0x10U == buf[1]) ? 41 : 16;
memcpy(&itap.length, buf, len); // transfer raw data to SITAP struct
// create a stream id if this is a header
if (41 == len)
stream_id = random.NewStreamID();
SDSVT dsvt; // destination
// sets most of the params
memcpy(dsvt.title, "DSVT", 4);
dsvt.config = (len==41) ? 0x10U : 0x20U;
memset(dsvt.flaga, 0U, 3U);
dsvt.id = 0x20;
dsvt.flagb[0] = 0x0;
dsvt.flagb[1] = 0x1;
dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3);
dsvt.streamid = htons(stream_id);
if (41 == len) { // header
dsvt.ctrl = 0x80;
memcpy(dsvt.hdr.flag, itap.header.flag, 3);
////////////////// Terminal or Access /////////////////////////
if (0 == memcmp(itap.header.r1, "DIRECT", 6)) {
// Terminal Mode!
memcpy(dsvt.hdr.rpt1, RPTR.c_str(), 7); // build r1
dsvt.hdr.rpt1[7] = RPTR_MOD; // with module
memcpy(dsvt.hdr.rpt2, RPTR.c_str(), 7); // build r2
dsvt.hdr.rpt2[7] = 'G'; // with gateway
if (' '==itap.header.ur[2] && ' '!=itap.header.ur[0]) {
// it's a command because it has as space in the 3rd position, we have to right-justify it!
// Terminal Mode left justifies short commands.
memset(dsvt.hdr.urcall, ' ', 8); // first file ur with spaces
if (' ' == itap.header.ur[1])
dsvt.hdr.urcall[7] = itap.header.ur[0]; // one char command, like "E" or "I"
else
memcpy(dsvt.hdr.urcall+6, itap.header.ur, 2); // two char command, like "HX" or "S0"
} else
memcpy(dsvt.hdr.urcall, itap.header.ur, 8); // ur is at least 3 chars
} else {
// Access Point Mode
memcpy(dsvt.hdr.rpt1, itap.header.r1, 8);
memcpy(dsvt.hdr.rpt2, itap.header.r2, 8);
memcpy(dsvt.hdr.urcall, itap.header.ur, 8);
dsvt.hdr.flag[0] &= ~0x40U; // clear this bit
}
memcpy(dsvt.hdr.mycall, itap.header.my, 8);
memcpy(dsvt.hdr.sfx, itap.header.nm, 4);
calcPFCS(dsvt.hdr.flag, dsvt.hdr.pfcs);
if (56 != Modem2Gate.Write(dsvt.title, 56)) {
printf("ERROR: ProcessITAP: Could not write gateway header packet\n");
return true;
}
if (LOG_QSO)
printf("Sent DSVT to gateway, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx);
} else if (16 == len) { // ambe
dsvt.ctrl = itap.voice.sequence;
memcpy(dsvt.vasd.voice, itap.voice.ambe, 12);
if (27 != Modem2Gate.Write(dsvt.title, 27)) {
printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n");
return true;
}
if (LOG_QSO && (dsvt.ctrl & 0x40))
printf("Sent dsvt end of streamid=%04x\n", ntohs(dsvt.streamid));
}
return false;
}
// process configuration file and return true if there was a problem
bool CQnetITAP::ReadConfig(const char *cfgFile)
{
CQnetConfigure cfg;
printf("Reading file %s\n", cfgFile);
if (cfg.Initialize(cfgFile))
return true;
const std::string estr; // an empty string
std::string type;
std::string itap_path("module_");
if (0 > assigned_module) {
// we need to find the lone itap module
for (int i=0; i<3; i++) {
std::string test(itap_path);
test.append(1, 'a'+i);
if (cfg.KeyExists(test)) {
cfg.GetValue(test, estr, type, 1, 16);
if (type.compare("itap"))
continue; // this ain't it!
itap_path.assign(test);
assigned_module = i;
break;
}
}
if (0 > assigned_module) {
fprintf(stderr, "Error: no 'itap' module found\n!");
return true;
}
} else {
// make sure itap module is defined
itap_path.append(1, 'a' + assigned_module);
if (cfg.KeyExists(itap_path)) {
cfg.GetValue(itap_path, estr, type, 1, 16);
if (type.compare("itap")) {
fprintf(stderr, "%s = %s is not 'itap' type!\n", itap_path.c_str(), type.c_str());
return true;
}
} else {
fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module);
return true;
}
}
RPTR_MOD = 'A' + assigned_module;
cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX);
cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX);
cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX);
itap_path.append("_callsign");
if (cfg.KeyExists(itap_path)) {
if (cfg.GetValue(itap_path, type, RPTR, 3, 6))
return true;
} else {
itap_path.assign("ircddb_login");
if (cfg.KeyExists(itap_path)) {
if (cfg.GetValue(itap_path, estr, RPTR, 3, 6))
return true;
}
}
int l = RPTR.length();
if (l<3 || l>6) {
printf("Call '%s' is invalid length!\n", RPTR.c_str());
return true;
} else {
for (int i=0; i<l; i++) {
if (islower(RPTR[i]))
RPTR[i] = toupper(RPTR[i]);
}
RPTR.resize(CALL_SIZE, ' ');
}
cfg.GetValue("log_qso", estr, LOG_QSO);
cfg.GetValue("log_debug", estr, LOG_DEBUG);
return false;
}
void CQnetITAP::SignalCatch(const int signum)
{
if ((signum == SIGTERM) || (signum == SIGINT) || (signum == SIGHUP))
keep_running = false;
exit(0);
}
void CQnetITAP::calcPFCS(const unsigned char *packet, unsigned char *pfcs)
{
unsigned short crc_dstar_ffff = 0xffff;
unsigned short tmp, short_c;
unsigned short crc_tabccitt[256] = {
0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7,
0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876,
0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5,
0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974,
0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3,
0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72,
0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1,
0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70,
0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff,
0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e,
0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd,
0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c,
0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb,
0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a,
0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9,
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78
};
for (int i = 0; i < 39 ; i++) {
short_c = 0x00ff & (unsigned short)packet[i];
tmp = (crc_dstar_ffff & 0x00ff) ^ short_c;
crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp];
}
crc_dstar_ffff = ~crc_dstar_ffff;
tmp = crc_dstar_ffff;
pfcs[0] = (unsigned char)(crc_dstar_ffff & 0xff);
pfcs[1] = (unsigned char)((tmp >> 8) & 0xff);
return;
}
int main(int argc, const char **argv)
{
setbuf(stdout, NULL);
if (2 != argc) {
fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]);
return 1;
}
if ('-' == argv[1][0]) {
printf("\nQnetITAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", ITAP_VERSION);
printf("QnetITAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n");
return 0;
}
const char *qn = strstr(argv[0], "qnitap");
if (NULL == qn) {
fprintf(stderr, "Error finding 'qnitap' in %s!\n", argv[0]);
return 1;
}
qn += 6;
int assigned_module;
switch (*qn) {
case NULL:
assigned_module = -1;
break;
case 'a':
assigned_module = 0;
break;
case 'b':
assigned_module = 1;
break;
case 'c':
assigned_module = 2;
break;
default:
fprintf(stderr, "assigned module must be a, b or c\n");
return 1;
}
CQnetITAP qnitap(assigned_module);
qnitap.Run(argv[1]);
printf("%s is closing.\n", argv[0]);
return 0;
}

@ -0,0 +1,147 @@
/*
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <atomic>
#include <cstring>
#include <string>
#include <queue>
#include <netinet/in.h>
#include "Random.h" // for streamid generation
#include "UnixDgramSocket.h"
#define CALL_SIZE 8
#define IP_SIZE 15
enum REPLY_TYPE {
RT_TIMEOUT,
RT_ERROR,
RT_UNKNOWN,
RT_HEADER,
RT_DATA,
RT_HEADER_ACK,
RT_DATA_ACK,
RT_PONG
};
// Icom Terminal and Access Point Mode data structure
#pragma pack(push, 1)
typedef struct itap_tag {
unsigned char length;
// 41 for header
// 16 for voice
unsigned char type;
// 0x03U pong
// 0x10U header from icom
// 0x11U acknowledgment
// 0x12U data from icom (it's EOT if voice.sequence bit 0x40 is set)
// 0x13U acknowledgment
// 0x20U header to icom
// 0x21U header acknowledgment
// 0x22U data to icom
// 0x23U data acknowledgment
union {
struct {
unsigned char flag[3];
unsigned char r2[8];
unsigned char r1[8];
unsigned char ur[8];
unsigned char my[8];
unsigned char nm[4];
} header;
struct {
unsigned char counter; // ordinal counter is reset with each header
unsigned char sequence; // is modulo 21
unsigned char ambe[9];
unsigned char text[3];
} voice;
};
} SITAP;
#pragma pack(pop)
class CFrame
{
public:
CFrame(const unsigned char *buf) {
memcpy(&frame.length, buf, buf[0]);
}
CFrame(const CFrame &from) {
memcpy(&frame.length, from.data(), from.size());
}
~CFrame() {}
size_t size() const { return (size_t)frame.length; }
const unsigned char *data() const { return &frame.length; }
private:
SITAP frame;
};
class CQnetITAP
{
public:
// functions
CQnetITAP(int mod);
~CQnetITAP();
void Run(const char *cfgfile);
// data
static std::atomic<bool> keep_running;
private:
int assigned_module;
// functions
bool Initialize(const char *cfgfile);
static void SignalCatch(const int signum);
bool ProcessGateway(const int len, const unsigned char *raw);
bool ProcessITAP(const unsigned char *raw);
int OpenITAP();
int SendTo(const unsigned char *buf);
REPLY_TYPE GetITAPData(unsigned char *buf);
void calcPFCS(const unsigned char *packet, unsigned char *pfcs);
void DumpSerialPacket(const char *title, const unsigned char *);
// read configuration file
bool ReadConfig(const char *);
// config data
char RPTR_MOD;
std::string ITAP_DEVICE, RPTR;
bool LOG_QSO, LOG_DEBUG;
// parameters
int serfd;
unsigned char tapcounter;
// helpers
CRandom random;
// unix sockets
std::string modem2gate, gate2modem;
CUnixDgramWriter Modem2Gate;
CUnixDgramReader Gate2Modem;
// Queue
std::queue<CFrame> queue;
bool acknowledged;
};

File diff suppressed because it is too large Load Diff

@ -0,0 +1,183 @@
#pragma once
/*
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <regex.h>
#include <string>
#include <map>
#include <vector>
#include <set>
#include <atomic>
#include <netinet/in.h>
#include "QnetTypeDefs.h"
#include "SEcho.h"
#include "Random.h"
#include "UnixDgramSocket.h"
#include "SockAddress.h"
#include "Timer.h"
/*** version number must be x.xx ***/
#define CALL_SIZE 8
#define IP_SIZE 15
#define QUERY_SIZE 56
#define MAXHOSTNAMELEN 64
#define TIMEOUT 50
#define LH_MAX_SIZE 39
typedef struct refdsvt_tag {
unsigned char head[2];
SDSVT dsvt;
} SREFDSVT;
typedef struct to_remote_g2_tag {
char cs[CALL_SIZE + 1];
CSockAddress addr;
char from_mod, to_mod;
short countdown;
bool auto_link, is_connected;
unsigned short in_streamid; // incoming from remote systems
unsigned short out_streamid; // outgoing to remote systems
} STOREMOTE;
// This is the data payload in the map: inbound_list
// This is for inbound dongles
typedef struct inbound_tag {
char call[CALL_SIZE + 1]; // the callsign of the remote
CSockAddress addr; // IP and port of remote
short countdown; // if countdown expires, the connection is terminated
char mod; // A B C This user talked on this module
char client; // dvap, dvdongle
} SINBOUND;
class CQnetLink {
public:
// functions
CQnetLink();
~CQnetLink();
bool Init(const char *cfgfile);
void Process();
void Shutdown();
private:
// functions
void ToUpper(std::string &s);
void UnpackCallsigns(const std::string &str, std::set<std::string> &set, const std::string &delimiters = ",");
void PrintCallsigns(const std::string &key, const std::set<std::string> &set);
bool load_gwys(const std::string &filename);
void calcPFCS(unsigned char *packet, int len);
bool read_config(const char *);
bool srv_open();
void srv_close();
static void sigCatch(int signum);
void g2link(const char from_mod, const char *call, const char to_mod);
void print_status_file();
void send_heartbeat();
bool resolve_rmt(const char *name, const unsigned short port, CSockAddress &addr);
void rptr_ack(short i);
void PlayAudioNotifyThread(char *msg);
void AudioNotifyThread(SECHO &edata);
void RptrAckThread(char *arg);
/* configuration data */
std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir;
bool only_admin_login, only_link_unlink, qso_details, log_debug, bool_rptr_ack, announce;
bool dplus_authorize, dplus_reflectors, dplus_repeaters, dplus_priority;
unsigned short rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port;
int delay_between, delay_before;
std::string link_at_startup[3];
unsigned int max_dongles, saved_max_dongles;
int rf_inactivity_timer[3];
const unsigned char REF_ACK[3] = { 3, 96, 0 };
// the Key in this inbound_list map is the unique IP address of the remote
std::map<std::string, SINBOUND *> inbound_list;
std::set<std::string> admin, link_unlink_user, link_blacklist;
std::map<std::string, std::string> dt_lh_list;
char notify_msg[3][64];
STOREMOTE to_remote_g2[3];
// broadcast for data arriving from xrf to local rptr
struct brd_from_xrf_tag {
unsigned short xrf_streamid; // streamid from xrf
unsigned short rptr_streamid[2]; // generated streamid to rptr(s)
} brd_from_xrf;
SDSVT from_xrf_torptr_brd;
short brd_from_xrf_idx;
// broadcast for data arriving from local rptr to xrf
struct brd_from_rptr_tag {
unsigned short from_rptr_streamid;
unsigned short to_rptr_streamid[2];
} brd_from_rptr;
SDSVT fromrptr_torptr_brd;
short brd_from_rptr_idx;
struct tracing_tag {
unsigned short streamid;
time_t last_time; // last time RF user talked
} tracing[3];
// input from remote
int xrf_g2_sock, ref_g2_sock, dcs_g2_sock;
CSockAddress fromDst4;
// unix sockets to gateway
std::string link2gate, gate2link;
CUnixDgramReader Gate2Link;
CUnixDgramWriter Link2Gate;
// input from our own local repeater
struct sockaddr_in fromRptr;
fd_set fdset;
struct timeval tv;
static std::atomic<bool> keep_running;
// Used to validate incoming donglers
regex_t preg;
// the map of remotes
// key is the callsign, data is the host
std::map<std::string, std::string> gwy_list;
unsigned char queryCommand[QUERY_SIZE];
// START: TEXT crap
char dtmf_mycall[3][CALL_SIZE + 1];
bool new_group[3];
int header_type;
bool GPS_seen[3];
unsigned char tmp_txt[3];
char *p_tmp2;
// END: TEXT crap
// this is used for the "dashboard and qso_details" to avoid processing multiple headers
struct old_sid_tag {
unsigned short sid;
} old_sid[3];
CRandom Random;
std::vector<unsigned long> speak;
};

@ -0,0 +1,909 @@
/*
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* CQnetModem is inspired by {Modem,MMDVMHost}.cpp in
* Jonathan Naylor's brilliant MMDVMHost that is...
* Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <exception>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <csignal>
#include <ctime>
#include <cstdlib>
#include <netdb.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <termios.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <thread>
#include <chrono>
#include "QnetModem.h"
#include "QnetConfigure.h"
#define MODEM_VERSION "QnetModem-0.1.3"
#define MAX_RESPONSES 30
std::atomic<bool> CQnetModem::keep_running(true);
const unsigned char FRAME_START = 0xE0U;
const unsigned char TYPE_VERSION = 0x00U;
const unsigned char TYPE_STATUS = 0x01U;
const unsigned char TYPE_CONFIG = 0x02U;
const unsigned char TYPE_MODE = 0x03U;
const unsigned char TYPE_FREQ = 0x04U;
const unsigned char TYPE_CWID = 0x0AU;
const unsigned char TYPE_HEADER = 0x10U;
const unsigned char TYPE_DATA = 0x11U;
const unsigned char TYPE_LOST = 0x12U;
const unsigned char TYPE_EOT = 0x13U;
const unsigned char TYPE_ACK = 0x70U;
const unsigned char TYPE_NACK = 0x7FU;
CQnetModem::CQnetModem(int mod)
: assigned_module(mod)
, dstarSpace(0U)
, g2_is_active(false)
{
}
CQnetModem::~CQnetModem()
{
}
bool CQnetModem::VoicePacketIsSync(const unsigned char *text)
{
return *text==0x55U && *(text+1)==0x2DU && *(text+2)==0x16U;
}
bool CQnetModem::GetBufferSize()
{
std::this_thread::sleep_for(std::chrono::seconds(2));
for (int i=0; i<6; i++) {
SMODEM frame;
frame.start = FRAME_START;
frame.length = 0x3U;
frame.type = TYPE_STATUS;
if (3 != SendToModem(&frame.start))
return true;
for (int count = 0; count < MAX_RESPONSES; count++) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
EModemResponse resp = GetModemData(&frame.start, sizeof(SVERSION));
if (resp == EModemResponse::status) {
dstarSpace = frame.status.dsrsize;
printf("D-Star buffer will hold %u voice frames\n", dstarSpace);
return false;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
}
fprintf(stderr, "Unable to read the firmware version after six attempts\n");
return true;
}
bool CQnetModem::GetVersion()
{
std::this_thread::sleep_for(std::chrono::seconds(2));
for (int i=0; i<6; i++) {
SVERSION frame;
frame.start = FRAME_START;
frame.length = 0x3U;
frame.type = TYPE_VERSION;
if (3 != SendToModem(&frame.start))
return true;
for (int count = 0; count < MAX_RESPONSES; count++) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
EModemResponse resp = GetModemData(&frame.start, sizeof(SVERSION));
if (resp == EModemResponse::version && frame.length > 14U) {
frame.version[frame.length-4U] = '\0'; // just to make sure!
if (0 == memcmp(frame.version, "MMDVM ", 6U))
hardwareType = EHardwareType::mmdvm;
else if (0 == memcmp(frame.version, "DVMEGA", 6U))
hardwareType = EHardwareType::dvmega;
else if (0 == memcmp(frame.version, "ZUMspot", 7U))
hardwareType = EHardwareType::zumspot;
else if (0 == memcmp(frame.version, "MMDVM_HS_Hat", 12U))
hardwareType = EHardwareType::hs_hat;
else if (0 == memcmp(frame.version, "MMDVM_HS_Dual_Hat", 17U))
hardwareType = EHardwareType::hs_dual_hat;
else if (0 == memcmp(frame.version, "Nano_hotSPOT", 12U))
hardwareType = EHardwareType::nano_hs;
else if (0 == memcmp(frame.version, "Nano_DV", 7U))
hardwareType = EHardwareType::nano_dv;
else if (0 == memcmp(frame.version, "MMDVM_HS-", 9U))
hardwareType = EHardwareType::mmdvm_hs;
else {
hardwareType = EHardwareType::unknown;
}
printf("MMDVM protocol version: %u, Modem: %s\n", (unsigned int)frame.protocol, (char *)frame.version);
return false;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
}
fprintf(stderr, "Unable to read the firmware version after six attempts\n");
return true;
}
bool CQnetModem::SetFrequency()
{
uint32_t pocsagFrequency = 433000000U;
SMODEM frame;
frame.start = FRAME_START;
frame.type = TYPE_FREQ;
if (hardwareType == EHardwareType::dvmega)
frame.length = 12U;
else {
frame.frequency.level = 255U;
frame.frequency.ps = __builtin_bswap32(htonl(pocsagFrequency));
frame.length = 17U;
}
frame.frequency.zero = 0x0U;
uint32_t rx_frequency = (uint32_t)((RX_FREQUENCY + RX_OFFSET) * 1000000.0);
frame.frequency.rx = __builtin_bswap32(htonl(rx_frequency));
uint32_t tx_frequency = (uint32_t)((TX_FREQUENCY + TX_OFFSET) * 1000000.0);
frame.frequency.tx = __builtin_bswap32(htonl(tx_frequency));
if ((int)frame.length != SendToModem(&frame.start))
return true;
int count = 0;
bool got_ack = false;
while (! got_ack) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
switch (GetModemData(&frame.start, sizeof(SMODEM))) {
case EModemResponse::ack:
got_ack = true;
break;
case EModemResponse::nack:
fprintf(stderr, "SET_FREQ failed, returned NACK reason %u\n", frame.nack.reason);
return true;
default:
if (++count >= MAX_RESPONSES) {
fprintf(stderr, "The MMDVM is not responding to the SET_FREQ command!\n");
return true;
}
break;
}
}
printf("Modem frequencies set: rx=%u(%u) tx=%u(%u) Hz\n", (uint32_t)(1.0E6 * RX_FREQUENCY), rx_frequency, (uint32_t)(1.0E6 * TX_FREQUENCY), tx_frequency);
return false;
}
bool CQnetModem::Initialize(const char *cfgfile)
{
if (ReadConfig(cfgfile))
return true;
struct sigaction act;
act.sa_handler = &CQnetModem::SignalCatch;
sigemptyset(&act.sa_mask);
if (sigaction(SIGTERM, &act, 0) != 0) {
printf("sigaction-TERM failed, error=%d\n", errno);
return true;
}
if (sigaction(SIGHUP, &act, 0) != 0) {
printf("sigaction-HUP failed, error=%d\n", errno);
return true;
}
if (sigaction(SIGINT, &act, 0) != 0) {
printf("sigaction-INT failed, error=%d\n", errno);
return true;
}
Modem2Gate.SetUp(modem2gate.c_str());
if (Gate2Modem.Open(gate2modem.c_str()))
return true;
serfd = OpenModem();
if (serfd < 0)
return true;
if (GetVersion())
return true;
if (SetFrequency())
return true;
if (SetConfiguration())
return true;
if (GetBufferSize())
return true;
return false;
}
bool CQnetModem::SetConfiguration()
{
SMODEM frame;
memset(&frame.start, 0, sizeof(SMODEM)); // star with a clean slate
frame.start = FRAME_START;
frame.length = 21U;
frame.type = TYPE_CONFIG;
if (RX_INVERT)
frame.config.flags |= 0x01U;
if (TX_INVERT)
frame.config.flags |= 0x02U;
if (PTT_INVERT)
frame.config.flags |= 0x04U;
if (! DUPLEX)
frame.config.flags |= 0x80U;
frame.config.mode = 0x1U; // Only D-Star is enabled!
frame.config.tx_delay = (unsigned char)(TX_DELAY / 10); // In 10ms units
frame.config.init_mode = 0x1U; // yup, just D-Star
frame.config.rx_level = (unsigned char)RX_LEVEL;
frame.config.osc_offset = 128U; // Was OscOffset
frame.config.dstar_tx_level = (unsigned char)TX_LEVEL;
frame.config.tx_dc_offset = 128U;
frame.config.rx_dc_offset = 128U;
// CUtils::dump(1U, "Written", buffer, 21U);
if (21 != SendToModem(&frame.start))
return false;
int count = 0;
bool got_ack = false;
while (! got_ack) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
switch (GetModemData(&frame.start, sizeof(SMODEM))) {
case EModemResponse::ack:
got_ack = true;
break;
case EModemResponse::nack:
fprintf(stderr, "SET_CONFIG failed, returned NACK reason %u\n", frame.nack.reason);
return true;
default:
if (++count >= MAX_RESPONSES) {
fprintf(stderr, "The MMDVM is not responding to the SET_CONFIG command!\n");
return true;
}
break;
}
}
printf("Modem configuration set for D-Star only\n");
return false;
}
int CQnetModem::OpenModem()
{
int fd = open(MODEM_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_SYNC, 0);
if (fd < 0) {
printf("Failed to open device [%s], error=%d, message=%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno));
return -1;
}
if (isatty(fd) == 0) {
printf("Device %s is not a tty device\n", MODEM_DEVICE.c_str());
close(fd);
return -1;
}
static termios t;
if (tcgetattr(fd, &t) < 0) {
printf("tcgetattr failed for %s, error=%d, message-%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno));
close(fd);
return -1;
}
t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG);
t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY);
t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
t.c_cflag |= CS8;
t.c_oflag &= ~(OPOST);
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 10;
cfsetospeed(&t, B115200);
cfsetispeed(&t, B115200);
if (tcsetattr(fd, TCSANOW, &t) < 0) {
printf("tcsetattr failed for %s, error=%dm message=%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno));
close(fd);
return -1;
}
return fd;
}
EModemResponse CQnetModem::GetModemData(unsigned char *buf, unsigned int size)
{
if (size < 4U) {
fprintf(stderr, "Buffer size, %u is too small\n", size);
return EModemResponse::error;
}
// Get the start byte
int ret = read(serfd, buf, 1U);
if (ret < 0) {
fprintf(stderr, "Error when reading frame start byte: %s\n", strerror(errno));
return EModemResponse::error;
} else if (ret == 0) {
printf("READ START RETURNED A ZERO!\n");
return EModemResponse::timeout;
} else if (buf[0] != FRAME_START)
return EModemResponse::timeout;
//get the length byte
ret = read(serfd, buf+1, 1U);
if (ret < 0) {
fprintf(stderr, "Error when reading frame length: %s\n", strerror(errno));
return EModemResponse::error;
} else if (ret == 0) {
printf("READ LENGTH RETURNED A ZERO!\n");
return(EModemResponse::timeout);
}
// is the packet size bigger than a D-Star header (44 bytes)?
unsigned int junk_count = ((unsigned int)buf[1] > size) ? (unsigned int)buf[1] - size : 0;
// get the type byte
ret = read(serfd, buf+2, 1U);
if (ret < 0) {
fprintf(stderr, "Error when reading frame type: %s\n", strerror(errno));
return EModemResponse::error;
} else if (ret == 0) {
printf("READ TYPE RETURNED A ZERO!\n");
return(EModemResponse::timeout);
}
// get the data
unsigned int length = buf[1];
unsigned int offset = 3;
while (offset < length) {
ret = read(serfd, buf + offset, length - offset);
if (ret < 0) {
printf("Error when reading data: %s\n", strerror(errno));
return EModemResponse::error;
}
if (ret == 0) {
printf("READ DATA RETURNED A ZERO!\n");
return(EModemResponse::timeout);
} else
offset += ret;
}
while (junk_count) {
unsigned char junk[8];
ret = read(serfd, junk, (junk_count > 8U) ? 8U : junk_count);
if (ret < 0) {
printf("Error when reading junk: %s\n", strerror(errno));
return EModemResponse::error;
} else if (ret == 0) {
printf("READ junk RETURNED A ZERO!\n");
return(EModemResponse::timeout);
} else {
junk_count -= (unsigned int)ret;
}
}
switch (buf[2]) {
case TYPE_ACK:
return EModemResponse::ack;
case TYPE_NACK:
return EModemResponse::nack;
case TYPE_HEADER:
return EModemResponse::header;
case TYPE_DATA:
return EModemResponse::data;
case TYPE_LOST:
return EModemResponse::lost;
case TYPE_EOT:
return EModemResponse::eot;
case TYPE_VERSION:
return EModemResponse::version;
case TYPE_STATUS:
return EModemResponse::status;
default:
return EModemResponse::error;
};
}
void CQnetModem::Run(const char *cfgfile)
{
if (Initialize(cfgfile))
return;
int ug2m = Gate2Modem.GetFD();
printf("gate2modem=%d, serial=%d\n", ug2m, serfd);
keep_running = true;
CTimer statusTimer;
CTimer deadTimer;
while (keep_running) {
SMODEM frame;
frame.start = FRAME_START;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(serfd, &readfds);
FD_SET(ug2m, &readfds);
int maxfs = (serfd > ug2m) ? serfd : ug2m;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 3000; // select will return a zero after 3 msec of inactivity
// don't care about writefds and exceptfds:
int ret = select(maxfs+1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno));
break;
}
// check for a dead or disconnected radio
if (10.0 < deadTimer.time()) {
printf("no activity from radio for 10 sec. Exiting...\n");
keep_running = false;
}
if (keep_running && FD_ISSET(serfd, &readfds)) {
deadTimer.start();
switch (GetModemData(&frame.start, sizeof(SMODEM))) {
case EModemResponse::data:
case EModemResponse::header:
case EModemResponse::eot:
case EModemResponse::lost:
if (ProcessModem(frame))
keep_running = false;
break;
case EModemResponse::status:
if (frame.status.flags & 0x02U)
fprintf(stderr, "Modem ADC levels have overflowed\n");
if (frame.status.flags & 0x04U)
fprintf(stderr, "Modem RX buffer has overflowed\n");
if (frame.status.flags & 0x08U)
fprintf(stderr, "Modem TX buffer has overflowed\n");
if (frame.status.flags & 0x20U)
fprintf(stderr, "Modem DAC levels have overflowed\n");
dstarSpace = frame.status.dsrsize;
break;
default:
break;
}
FD_CLR(serfd, &readfds);
}
if (keep_running && FD_ISSET(ug2m, &readfds)) {
SDSVT dsvt;
ssize_t len = Gate2Modem.Read(dsvt.title, sizeof(SDSVT));
if (len <= 0) {
break;
}
if (0 == memcmp(dsvt.title, "DSVT", 4) && dsvt.id==0x20U && (dsvt.config==0x10U || dsvt.config==0x20U) && (len==56 || len==27)) {
ProcessGateway(dsvt);
} else {
fprintf(stderr, "Unexpected data, returned %d bytes from the gateway: %02x", int(len), *dsvt.title);
for (ssize_t i=1; i<len; i++)
fprintf(stderr, " %02x", *(dsvt.title + int(i)));
fprintf(stderr, "\n");
break;
}
FD_CLR(ug2m, &readfds);
}
if (keep_running) {
//if (g2_is_active && PacketWait.time() > packet_wait) {
// // g2 has timed out
// frame.length = 3U;
// frame.type = TYPE_LOST;
// queue.push(CFrame(&frame.start));
// g2_is_active = false;
//}
if (! queue.empty()) {
// send queued D-Star frames to modem
CFrame cframe = queue.front();
const unsigned char type = cframe.type();
if ((type==TYPE_HEADER && dstarSpace>3U) || ((type==TYPE_DATA || type==TYPE_EOT || type==TYPE_LOST) && dstarSpace>0U)) {
SendToModem(cframe.data());
queue.pop();
dstarSpace -= (type==TYPE_HEADER) ? 4U : 1U;
}
}
if (statusTimer.time() > 0.25) {
// request a status update every 250 milliseconds
frame.length = 3U;
frame.type = TYPE_STATUS;
if (3 != SendToModem(&frame.start))
keep_running = false;
statusTimer.start();
}
}
}
close(serfd);
Gate2Modem.Close();
}
int CQnetModem::SendToModem(const unsigned char *buf)
{
ssize_t n;
size_t sent = 0;
ssize_t length = buf[1];
while ((ssize_t)sent < length) {
n = write(serfd, buf + sent, length - sent);
if (n < 0) {
if (EAGAIN != errno) {
printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno));
return -1;
}
}
sent += n;
}
return length;
}
void CQnetModem::ProcessGateway(const SDSVT &dsvt)
{
static std::string superframe;
SMODEM frame; // destination
frame.start = FRAME_START;
if (0x10U == dsvt.config) { // write a Header packet
superframe.clear();
frame.length = 44U;
frame.type = TYPE_HEADER;
memcpy(frame.header.flag, dsvt.hdr.flag, 3);
memcpy(frame.header.r1, dsvt.hdr.rpt2, 8);
memcpy(frame.header.r2, dsvt.hdr.rpt1, 8);
memcpy(frame.header.ur, dsvt.hdr.urcall, 8);
memcpy(frame.header.my, dsvt.hdr.mycall, 8);
memcpy(frame.header.nm, dsvt.hdr.sfx, 4);
memcpy(frame.header.pfcs, dsvt.hdr.pfcs, 2);
queue.push(CFrame(&frame.start));
PacketWait.start();
g2_is_active = true;
if (LOG_QSO)
printf("Queued to %s flags=%02x:%02x:%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MODEM_DEVICE.c_str(), frame.header.flag[0], frame.header.flag[1], frame.header.flag[2], frame.header.ur, frame.header.r2, frame.header.r1, frame.header.my, frame.header.nm);
} else { // write a voice data packet
if (g2_is_active) {
//const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U };
if (dsvt.ctrl & 0x40U) {
if (LOG_DEBUG && superframe.size())
printf("Final order: %s\n", superframe.c_str());
frame.length = 3U;
frame.type = TYPE_EOT;
g2_is_active = false;
if (LOG_QSO)
printf("Queued modem end of transmission\n");
} else {
frame.length = 15U;
frame.type = TYPE_DATA;
memcpy(frame.voice.ambe, dsvt.vasd.voice, 12);
if (LOG_DEBUG) {
const unsigned int ctrl = dsvt.ctrl & 0x3FU;
if (VoicePacketIsSync(dsvt.vasd.text)) {
if (superframe.size() > 65) {
printf("Frame order: %s\n", superframe.c_str());
superframe.clear();
}
const char *ch = "#abcdefghijklmnopqrstuvwxyz";
superframe.append(1, (ctrl<27U) ? ch[ctrl] : '%');
} else {
const char *ch = "!ABCDEFGHIJKLMNOPQRSTUVWXYZ";
superframe.append(1, (ctrl<27U) ? ch[ctrl] : '*');
}
}
}
queue.push(CFrame(&frame.start));
PacketWait.start();
}
}
}
bool CQnetModem::ProcessModem(const SMODEM &frame)
{
static bool in_stream = false;
static bool first_voice_packet = false;
static short stream_id = 0U;
static unsigned char nextctrl = 21U;
// create a stream id if this is a header
if (frame.type == TYPE_HEADER)
stream_id = random.NewStreamID();
SDSVT dsvt; // destination
// sets most of the params
memcpy(dsvt.title, "DSVT", 4);
memset(dsvt.flaga, 0U, 3U);
dsvt.id = 0x20U;
dsvt.flagb[0] = 0x0U;
dsvt.flagb[1] = 0x1U;
dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1U : (('C'==RPTR_MOD) ? 0x2U : 0x3U);
dsvt.streamid = htons(stream_id);
if (frame.type == TYPE_HEADER) { // header
nextctrl = 21U;
in_stream = first_voice_packet = true;
dsvt.config = 0x10U;
dsvt.ctrl = 0x80U;
memcpy(dsvt.hdr.flag, frame.header.flag, 3);
dsvt.hdr.flag[0] &= ~0x40U; // clear this bit
memcpy(dsvt.hdr.rpt1, frame.header.r1, 8);
memcpy(dsvt.hdr.rpt2, frame.header.r2, 8);
memcpy(dsvt.hdr.urcall, frame.header.ur, 8);
memcpy(dsvt.hdr.mycall, frame.header.my, 8);
memcpy(dsvt.hdr.sfx, frame.header.nm, 4);
memcpy(dsvt.hdr.pfcs, frame.header.pfcs, 2);
if (56 != Modem2Gate.Write(dsvt.title, 56)) {
printf("ERROR: ProcessModem: Could not write gateway header packet\n");
return true;
}
if (LOG_QSO)
printf("Sent DSVT to gateway, streamid=%04x flags=%02x:%02x:%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx);
} else if (in_stream && (frame.type==TYPE_DATA || frame.type==TYPE_EOT || frame.type==TYPE_LOST)) { // ambe
const unsigned char sync[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x55U,0x2DU,0x16U };
const unsigned char silence[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x70U,0x4FU,0x93U };
dsvt.config = 0x20U;
if (frame.type == TYPE_DATA) {
if (first_voice_packet) { // make sure the first voice packet is a sync frame
if (! VoicePacketIsSync(frame.voice.text)) { // create a quite sync voice packet
if (LOG_DEBUG)
printf("Warning: Inserting missing frame sync after header\n");
dsvt.ctrl = 0U;
memcpy(dsvt.vasd.voice, sync, 12U);
Modem2Gate.Write(dsvt.title, 27);
nextctrl = 0x1U;
}
first_voice_packet = false;
}
if (VoicePacketIsSync(frame.voice.text)) {
if (nextctrl < 21U)
fprintf(stderr, "Warning: The last superframe had %u frames, inserting missing frame(s)\n", nextctrl);
memcpy(dsvt.vasd.voice, silence, 12U);
while (nextctrl < 21U) {
dsvt.ctrl = nextctrl++;
Modem2Gate.Write(dsvt.title, 27);
}
nextctrl = 0x0U;
}
if (nextctrl > 20U) {
fprintf(stderr, "Warning: nextctrl=%u, inserting missing sync frame\n", nextctrl);
dsvt.ctrl = 0U;
memcpy(dsvt.vasd.voice, sync, 12U);
Modem2Gate.Write(dsvt.title, 27);
nextctrl = 0x1U;
}
memcpy(dsvt.vasd.voice, frame.voice.ambe, 12);
} else {
if (frame.type == TYPE_LOST)
printf("Got a TYPE_LOST packet.\n");
if (0U == nextctrl) {
memcpy(dsvt.vasd.voice, sync, 12);
} else {
memcpy(dsvt.vasd.voice, silence, 12);
}
nextctrl |= 0x40U;
if (LOG_QSO) {
if (frame.type == TYPE_EOT)
printf("Sent DSVT end of streamid=%04x\n", ntohs(dsvt.streamid));
else
printf("Sent LOST end of streamid=%04x\n", ntohs(dsvt.streamid));
}
in_stream = false;
}
dsvt.ctrl = nextctrl++;
if (27 != Modem2Gate.Write(dsvt.title, 27)) {
printf("ERROR: ProcessModem: Could not write gateway voice packet\n");
return true;
}
} else {
if (in_stream) {
fprintf(stderr, "Warning! Unexpected frame: %02x", frame.start);
for (unsigned int i=1U; i<frame.length; i++)
fprintf(stderr, ":%02x", *(&frame.start + i));
fprintf(stderr, "\n");
}
}
return false;
}
// process configuration file and return true if there was a problem
bool CQnetModem::ReadConfig(const char *cfgFile)
{
CQnetConfigure cfg;
printf("Reading file %s\n", cfgFile);
if (cfg.Initialize(cfgFile))
return true;
const std::string estr; // an empty string
std::string type;
std::string modem_path("module_");
if (0 > assigned_module) {
// we need to find the lone mmdvmmodem module
for (int i=0; i<3; i++) {
std::string test(modem_path);
test.append(1, 'a'+i);
if (cfg.KeyExists(test)) {
cfg.GetValue(test, estr, type, 1, 16);
if (type.compare("mmdvmmodem"))
continue; // this ain't it!
modem_path.assign(test);
assigned_module = i;
break;
}
}
if (0 > assigned_module) {
fprintf(stderr, "Error: no 'mmdvmmodem' module found\n!");
return true;
}
} else {
// make sure mmdvmmodem module is defined
modem_path.append(1, 'a' + assigned_module);
if (cfg.KeyExists(modem_path)) {
cfg.GetValue(modem_path, estr, type, 1, 16);
if (type.compare("mmdvmmodem")) {
fprintf(stderr, "%s = %s is not 'mmdvmmodem' type!\n", modem_path.c_str(), type.c_str());
return true;
}
} else {
fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module);
return true;
}
}
RPTR_MOD = 'A' + assigned_module;
cfg.GetValue(modem_path+"_device", type, MODEM_DEVICE, 7, FILENAME_MAX);
cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX);
cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX);
if (cfg.GetValue(modem_path+"_tx_frequency", type, TX_FREQUENCY, 1.0, 6000.0))
return true; // we have to have a valid frequency
cfg.GetValue(modem_path+"_rx_frequency", type, RX_FREQUENCY, 0.0, 6000.0);
if (RX_FREQUENCY <= 0.0)
RX_FREQUENCY = TX_FREQUENCY;
cfg.GetValue(modem_path+"_tx_offset", type, TX_OFFSET, -10.0, 10.0);
cfg.GetValue(modem_path+"_rx_offset", type, RX_OFFSET, -10.0, 10.0);
cfg.GetValue(modem_path+"_duplex", type, DUPLEX);
cfg.GetValue(modem_path+"_rx_invert", type, RX_INVERT);
cfg.GetValue(modem_path+"_tx_invert", type, TX_INVERT);
cfg.GetValue(modem_path+"_ptt_invert", type, PTT_INVERT);
cfg.GetValue(modem_path+"_tx_delay", type, TX_DELAY, 0, 1000);
cfg.GetValue(modem_path+"_rx_level", type, RX_LEVEL, 0, 255);
cfg.GetValue(modem_path+"_tx_level", type, TX_LEVEL, 0, 255);
cfg.GetValue(modem_path+"_packet_wait", type, PACKET_WAIT, 18, 30);
packet_wait = 1.0E-3 * double(PACKET_WAIT);
modem_path.append("_callsign");
if (cfg.KeyExists(modem_path)) {
if (cfg.GetValue(modem_path, type, RPTR, 3, 6))
return true;
} else {
modem_path.assign("ircddb_login");
if (cfg.KeyExists(modem_path)) {
if (cfg.GetValue(modem_path, estr, RPTR, 3, 6))
return true;
}
}
int l = RPTR.length();
if (l<3 || l>6) {
printf("Call '%s' is invalid length!\n", RPTR.c_str());
return true;
} else {
for (int i=0; i<l; i++) {
if (islower(RPTR[i]))
RPTR[i] = toupper(RPTR[i]);
}
RPTR.resize(CALL_SIZE, ' ');
}
cfg.GetValue("log_qso", estr, LOG_QSO);
cfg.GetValue("log_debug", estr, LOG_DEBUG);
return false;
}
void CQnetModem::SignalCatch(const int signum)
{
if ((signum == SIGTERM) || (signum == SIGINT) || (signum == SIGHUP))
keep_running = false;
exit(0);
}
int main(int argc, const char **argv)
{
setbuf(stdout, NULL);
if (2 != argc) {
fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]);
return 1;
}
if ('-' == argv[1][0]) {
printf("\nQnetModem Version %s Copyright (C) 2019 by Thomas A. Early N7TAE\n", MODEM_VERSION);
printf("QnetModem comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("This is free software, and you are welcome to distribute it\n");
printf("under certain conditions that are discussed in the LICENSE file.\n\n");
return 0;
}
const char *qn = strstr(argv[0], "qnmodem");
if (NULL == qn) {
fprintf(stderr, "Error finding 'qnmodem' in %s!\n", argv[0]);
return 1;
}
qn += 7;
int assigned_module;
switch (*qn) {
case NULL:
assigned_module = -1;
break;
case 'a':
assigned_module = 0;
break;
case 'b':
assigned_module = 1;
break;
case 'c':
assigned_module = 2;
break;
default:
fprintf(stderr, "assigned module must be a, b or c\n");
return 1;
}
CQnetModem qnmodem(assigned_module);
qnmodem.Run(argv[1]);
printf("%s is closing.\n", argv[0]);
return 0;
}

@ -0,0 +1,238 @@
/*
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <atomic>
#include <cstring>
#include <string>
#include <queue>
#include <netinet/in.h>
#include "Random.h" // for streamid generation
#include "UnixDgramSocket.h"
#include "QnetTypeDefs.h"
#include "Timer.h"
#define CALL_SIZE 8
#define IP_SIZE 15
enum class EModemResponse {
ack,
nack,
timeout,
error,
header,
data,
lost,
eot,
status,
version
};
enum class EHardwareType {
mmdvm,
dvmega,
zumspot,
hs_hat,
hs_dual_hat,
nano_hs,
nano_dv,
mmdvm_hs,
unknown
};
// Icom Terminal and Access Point Mode data structure
#pragma pack(push, 1)
typedef struct version_tag {
unsigned char start;
unsigned char length;
unsigned char type;
unsigned char protocol;
unsigned char version[251];
} SVERSION;
typedef struct mmodem_tag {
unsigned char start; // always 0xEOU
unsigned char length; // 3 - 255
unsigned char type;
// 0x70U acknowledge from modem, ACK
// 0x7FU error from modem, NACK
// 0x00U version
// 0x01U status
// 0x02U configure
// 0x03U mode
// 0x04U frequency
// 0x10U header
// 0x11U data
// 0x12U transmission lost
// 0x13U transmission end
union {
unsigned char ack; // the type being acknowledged
unsigned char mode; // 0 idle, 1 dstar, 2 dmr, 3 ysf, 99 calibration
struct {
unsigned char ack; // the type being acknowledged
unsigned char reason; // reason for the NAK
// 1 - invalid command
// 2 - wrong mode
// 3 - command too long
// 4 - data incorrect
// 5 - Not enough buffer space
} nack;
// don't want to inflate the struct size, so it's here for reference only
//struct {
// unsigned char protocol_version;
// unsigned char version[250];
//} version;
struct {
unsigned char modes; // 0x1U dstar | 0x2 dmr | 0x4 system fusion
unsigned char status; // 0 idle, 1 dstar, 2 dmr, 3 system fusion, 99 calibration
unsigned char flags; // 0x1 Tx on, 0x2 adc overflow
unsigned char dsrsize; // dstar buffersize
unsigned char dm1size; // drm timeslot 1 buffersize
unsigned char dm2size; // dmr timeslot 2 buffersize
unsigned char ysfsize; // ysf buffersize
} status;
struct {
unsigned char flags; // 0x1 rx 0x2 tx 0x4 ptt 0x8 ysf lodev 0x10 debug 0x80 not duplex
unsigned char mode; // 0x1 dstar 0x2 drm 0x4 ysf 0x8 p25 0x10 nxdx 0x20 pocsag
unsigned char tx_delay; // tx delay in 10 millisecond increments
unsigned char init_mode; // inital state 0 idle 1 dstar 2 dmr 3 ysf 99 calibration
unsigned char rx_level; // rx input level 0-255
unsigned char cw_tx_level; // cw tx output
unsigned char color; // dmr color 0-15
unsigned char drm_delay;
unsigned char osc_offset; // 128U
unsigned char dstar_tx_level;
unsigned char dmr_tx_level;
unsigned char ysf_tx_level;
unsigned char p25_tx_level;
unsigned char tx_dc_offset;
unsigned char rx_dc_offset;
unsigned char nxdn_tx_level;
unsigned char ysf_tx_hang;
unsigned char pocsag_tx;
} config;
struct {
unsigned char zero; // should be zero;
uint32_t rx; // receive frequency
uint32_t tx; // transmitter frequency
unsigned char level; // rf level for pocsag?
uint32_t ps; // pocsag frequency, default 433000000U
} frequency;
struct {
unsigned char flag[3];
unsigned char r2[8];
unsigned char r1[8];
unsigned char ur[8];
unsigned char my[8];
unsigned char nm[4];
unsigned char pfcs[2];
} header;
struct {
unsigned char ambe[9];
unsigned char text[3];
} voice;
};
} SMODEM;
#pragma pack(pop)
class CFrame
{
public:
CFrame(const unsigned char *buf) {
memcpy(&frame.start, buf, buf[1]);
}
CFrame(const CFrame &from) {
memcpy(&frame.start, from.data(), from.size());
}
CFrame &operator=(const CFrame &from) {
memcpy(&frame.start, from.data(), from.size());
return *this;
}
~CFrame() {}
size_t size() const { return (size_t)frame.length; }
const unsigned char *data() const { return &frame.start; }
unsigned char type() { return frame.type; }
private:
SMODEM frame;
};
class CQnetModem
{
public:
// functions
CQnetModem(int mod);
~CQnetModem();
void Run(const char *cfgfile);
// data
static std::atomic<bool> keep_running;
private:
int assigned_module;
unsigned int dstarSpace;
bool g2_is_active;
// functions
bool VoicePacketIsSync(const unsigned char *);
bool Initialize(const char *cfgfile);
static void SignalCatch(const int signum);
void ProcessGateway(const SDSVT &dsvt);
bool ProcessModem(const SMODEM &frame);
int OpenModem();
int SendToModem(const unsigned char *buf);
EModemResponse GetModemData(unsigned char *buf, unsigned int size);
bool GetVersion();
bool GetBufferSize();
bool SetFrequency();
bool SetConfiguration();
// read configuration file
bool ReadConfig(const char *);
// config data
char RPTR_MOD;
std::string MODEM_DEVICE, RPTR;
double TX_FREQUENCY, RX_FREQUENCY, TX_OFFSET, RX_OFFSET, packet_wait;
int TX_DELAY, RX_LEVEL, TX_LEVEL, PACKET_WAIT;
bool DUPLEX, RX_INVERT, TX_INVERT, PTT_INVERT, LOG_QSO, LOG_DEBUG;
// parameters
EHardwareType hardwareType;
int serfd;
// helpers
CRandom random;
CTimer PacketWait;
// unix sockets
std::string modem2gate, gate2modem;
CUnixDgramWriter Modem2Gate;
CUnixDgramReader Gate2Modem;
// Queue
std::queue<CFrame> queue;
};

@ -32,13 +32,16 @@
#include <arpa/inet.h>
#include <errno.h>
#include "versions.h"
#include "QnetRelay.h"
#include "QnetTypeDefs.h"
#include "QnetConfigure.h"
#define RELAY_VERSION "QnetRelay-1.1.0"
std::atomic<bool> CQnetRelay::keep_running(true);
CQnetRelay::CQnetRelay() :
CQnetRelay::CQnetRelay(int mod) :
assigned_module(mod),
seed(time(NULL)),
COUNTER(0)
{
@ -116,22 +119,22 @@ int CQnetRelay::OpenSocket(const std::string &address, unsigned short port)
return fd;
}
void CQnetRelay::Run(const char *cfgfile)
bool CQnetRelay::Run(const char *cfgfile)
{
if (Initialize(cfgfile))
return;
return true;
msock = OpenSocket(MMDVM_IP, MMDVM_OUT_PORT);
if (msock < 0)
return;
return true;
gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT);
if (gsock < 0) {
::close(msock);
return;
}
Modem2Gate.SetUp(modem2gate.c_str());
if (Gate2Modem.Open(gate2modem.c_str()))
return true;
int fd = Gate2Modem.GetFD();
printf("msock=%d, gsock=%d\n", msock, gsock);
printf("msock=%d, gateway=%d\n", msock, fd);
keep_running = true;
@ -139,8 +142,8 @@ void CQnetRelay::Run(const char *cfgfile)
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(msock, &readfds);
FD_SET(gsock, &readfds);
int maxfs = (msock > gsock) ? msock : gsock;
FD_SET(fd, &readfds);
int maxfs = (msock > fd) ? msock : fd;
// don't care about writefds and exceptfds:
// and we'll wait as long as needed
@ -163,30 +166,26 @@ void CQnetRelay::Run(const char *cfgfile)
len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size);
if (len < 0) {
printf("ERROR: Run: recvfrom(mmdvm) return error %d, %s\n", errno, strerror(errno));
fprintf(stderr, "ERROR: Run: recvfrom(mmdvmhost) return error %d: %s\n", errno, strerror(errno));
break;
}
if (ntohs(addr.sin_port) != MMDVM_IN_PORT)
printf("DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT);
fprintf(stderr, "DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT);
}
if (FD_ISSET(gsock, &readfds)) {
len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size);
if (FD_ISSET(fd, &readfds)) {
len = Gate2Modem.Read(buf, 100);
if (len < 0) {
printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno));
fprintf(stderr, "ERROR: Run: Gate2Modem.Read() returned error %d: %s\n", errno, strerror(errno));
break;
}
if (ntohs(addr.sin_port) != G2_IN_PORT)
printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT);
}
if (len == 0) {
printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port));
fprintf(stderr, "DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port));
continue;
}
@ -194,7 +193,7 @@ void CQnetRelay::Run(const char *cfgfile)
//printf("read %d bytes from MMDVMHost\n", (int)len);
if (ProcessMMDVM(len, buf))
break;
} else if (0 == ::memcmp(buf, "DSTR", 4)) {
} else if (0 == ::memcmp(buf, "DSVT", 4)) {
//printf("read %d bytes from MMDVMHost\n", (int)len);
if (ProcessGateway(len, buf))
break;
@ -203,12 +202,13 @@ void CQnetRelay::Run(const char *cfgfile)
for (int i=0; i<4; i++)
title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.';
title[4] = '\0';
printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len);
fprintf(stderr, "DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len);
}
}
::close(msock);
::close(gsock);
Gate2Modem.Close();
return false;
}
int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port)
@ -229,43 +229,49 @@ int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, c
bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw)
{
if (29==len || 58==len) { //here is dstar data
SDSTR buf;
::memcpy(buf.pkt_id, raw, len); // transfer raw data to SDSTR struct
if (27==len || 56==len) { //here is dstar data
SDSVT dsvt;
::memcpy(dsvt.title, raw, len); // transfer raw data to SDSVT struct
SDSRP pkt; // destination
SDSRP dsrp; // destination
// fill in some inital stuff
::memcpy(pkt.title, "DSRP", 4);
pkt.voice.id = buf.vpkt.streamid;
pkt.voice.seq = buf.vpkt.ctrl;
if (29 == len) { // write an AMBE packet
pkt.tag = 0x21U;
if (pkt.voice.seq & 0x40)
// printf("INFO: ProcessGateway: sending voice end-of-stream\n");
;
else if (pkt.voice.seq > 20)
printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", pkt.voice.seq);
pkt.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf
memcpy(pkt.voice.ambe, buf.vpkt.vasd.voice, 12);
int ret = SendTo(msock, pkt.title, 21, MMDVM_IP, MMDVM_IN_PORT);
::memcpy(dsrp.title, "DSRP", 4);
dsrp.voice.id = dsvt.streamid; // voice or header is the same position
dsrp.voice.seq = dsvt.ctrl; // ditto
if (27 == len) { // write an AMBE packet
dsrp.tag = 0x21U;
if (log_qso && (dsrp.voice.seq & 0x40))
printf("Sent DSRP end of streamid=%04x\n", ntohs(dsrp.voice.id));
if ((dsrp.voice.seq & ~0x40U) > 20)
printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", dsrp.voice.seq);
dsrp.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf
memcpy(dsrp.voice.ambe, dsvt.vasd.voice, 12);
int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT);
if (ret != 21) {
printf("ERROR: ProcessGateway: Could not write AMBE mmdvm packet\n");
printf("ERROR: ProcessGateway: Could not write AMBE mmdvmhost packet\n");
return true;
}
} else { // write a Header packet
pkt.tag = 0x20U;
pkt.header.id = buf.vpkt.streamid;
if (pkt.header.seq) {
dsrp.tag = 0x20U;
if (dsrp.header.seq) {
// printf("DEBUG: ProcessGateway: unexpected pkt.header.seq %d, resetting to 0\n", pkt.header.seq);
pkt.header.seq = 0;
}
memcpy(pkt.header.flag, buf.vpkt.hdr.flag, 41);
int ret = SendTo(msock, pkt.title, 49, MMDVM_IP, MMDVM_IN_PORT);
dsrp.header.seq = 0;
}
//memcpy(dsrp.header.flag, dsvt.hdr.flag, 41);
memcpy(dsrp.header.flag, dsvt.hdr.flag, 3);
memcpy(dsrp.header.r1, dsvt.hdr.rpt1, 8);
memcpy(dsrp.header.r2, dsvt.hdr.rpt2, 8);
memcpy(dsrp.header.ur, dsvt.hdr.urcall, 8);
memcpy(dsrp.header.my, dsvt.hdr.mycall, 8);
memcpy(dsrp.header.nm, dsvt.hdr.sfx, 4);
memcpy(dsrp.header.pfcs, dsvt.hdr.pfcs, 2);
int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT);
if (ret != 49) {
printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n");
printf("ERROR: ProcessGateway: Could not write Header mmdvmhost packet\n");
return true;
}
printf("INFO: ProcessGateway: sent header to port %u pkt = '%s'\n", MMDVM_IN_PORT, std::string((char *)pkt.header.r2, 36).c_str());
if (log_qso)
printf("Sent DSRP to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MMDVM_IN_PORT, ntohs(dsrp.header.id), dsrp.header.ur, dsrp.header.r2, dsrp.header.r1, dsrp.header.my, dsrp.header.nm);
}
} else
@ -275,205 +281,124 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw)
bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw)
{
static short old_id = 0U;
static short stream_id = 0U;
SDSRP mpkt;
static unsigned short id = 0U;
SDSRP dsrp;
if (len < 65)
::memcpy(mpkt.title, raw, len); // transfer raw data to SDSRP struct
::memcpy(dsrp.title, raw, len); // transfer raw data to SDSRP struct
if (49==len || 21==len) {
// grab the stream id if this is a header
if (49 == len) {
stream_id = mpkt.header.id;
if (old_id == stream_id)
if (dsrp.header.id == id)
return false;
id = dsrp.header.id;
} else {
if (dsrp.voice.id != id)
return false;
old_id = stream_id;
}
SDSTR gpkt; // destination
SDSVT dsvt; // destination
// sets most of the params
::memcpy(gpkt.pkt_id, "DSTR", 4);
gpkt.counter = COUNTER++;
gpkt.flag[0] = 0x73;
gpkt.flag[1] = 0x12;
gpkt.flag[2] = 0x0;
gpkt.vpkt.icm_id = 0x20;
gpkt.vpkt.dst_rptr_id = 0x0;
gpkt.vpkt.snd_rptr_id = 0x1;
gpkt.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3);
gpkt.vpkt.streamid = stream_id;
::memcpy(dsvt.title, "DSVT", 4);
dsvt.config = (len==49) ? 0x10U : 0x20U;
memset(dsvt.flaga, 0U, 3U);
dsvt.id = 0x20U;
dsvt.flagb[0] = 0x0U;
dsvt.flagb[1] = 0x1U;
dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1U : (('C'==RPTR_MOD) ? 0x2U : 0x3U);
dsvt.streamid = id;
if (49 == len) { // header
gpkt.remaining = 0x30;
gpkt.vpkt.ctrl = 0x80;
::memcpy(gpkt.vpkt.hdr.flag, mpkt.header.flag, 41);
int ret = SendTo(msock, gpkt.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT);
if (ret != 58) {
dsvt.ctrl = 0x80;
//memcpy(dsvt.hdr.flag, dsrp.header.flag, 41);
memcpy(dsvt.hdr.flag, dsrp.header.flag, 3);
memcpy(dsvt.hdr.rpt1, dsrp.header.r1, 8);
memcpy(dsvt.hdr.rpt2, dsrp.header.r2, 8);
memcpy(dsvt.hdr.urcall, dsrp.header.ur, 8);
memcpy(dsvt.hdr.mycall, dsrp.header.my, 8);
memcpy(dsvt.hdr.sfx, dsrp.header.nm, 4);
memcpy(dsvt.hdr.pfcs, dsrp.header.pfcs, 2);
if (56 != Modem2Gate.Write(dsvt.title, 56)) {
printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n");
return true;
}
printf("INFO: ProcessMMDVM: sent header to port %u pkt = '%s'\n", G2_IN_PORT, std::string((char *)gpkt.vpkt.hdr.r2, 36).c_str());
if (log_qso)
printf("Sent DSVT streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx);
} else if (21 == len) { // ambe
gpkt.remaining = 0x16;
gpkt.vpkt.ctrl = mpkt.header.seq;
::memcpy(gpkt.vpkt.vasd.voice, mpkt.voice.ambe, 12);
int ret = SendTo(msock, gpkt.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT);
if (ret != 29) {
dsvt.ctrl = dsrp.header.seq;
memcpy(dsvt.vasd.voice, dsrp.voice.ambe, 12);
if (27 != Modem2Gate.Write(dsvt.title, 27)) {
printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n");
return true;
}
if (log_qso && dsvt.ctrl&0x40)
printf("Sent DSVT end of streamid=%04x\n", ntohs(dsvt.streamid));
}
} else if (len < 65 && mpkt.tag == 0xAU) {
} else if (len < 65 && dsrp.tag == 0xAU) {
// printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg);
} else
printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", len);
return false;
}
bool CQnetRelay::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%d]\n", path, value);
return true;
}
bool CQnetRelay::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%lg]\n", path, value);
return true;
}
bool CQnetRelay::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value)
{
if (! cfg.lookupValue(path, value))
value = default_value;
printf("%s = [%s]\n", path, value ? "true" : "false");
return true;
}
bool CQnetRelay::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value)
{
if (cfg.lookupValue(path, value)) {
int l = value.length();
if (l<min || l>max) {
printf("%s value '%s' is wrong size\n", path, value.c_str());
return false;
}
} else
value = default_value;
printf("%s = [%s]\n", path, value.c_str());
return true;
}
// process configuration file and return true if there was a problem
bool CQnetRelay::ReadConfig(const char *cfgFile)
{
Config cfg;
CQnetConfigure cfg;
printf("Reading file %s\n", cfgFile);
// Read the file. If there is an error, report it and exit.
try {
cfg.readFile(cfgFile);
}
catch(const FileIOException &fioex) {
printf("Can't read %s\n", cfgFile);
if (cfg.Initialize(cfgFile))
return true;
}
catch(const ParseException &pex) {
printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError());
return true;
}
std::string mmdvm_path, value;
int i;
for (i=0; i<3; i++) {
mmdvm_path = "module.";
mmdvm_path += ('a' + i);
if (cfg.lookupValue(mmdvm_path + ".type", value)) {
if (0 == strcasecmp(value.c_str(), "mmdvm"))
const std::string estr; // an empty GetDefaultString
std::string mmdvm_path("module_");
std::string type;
if (0 > assigned_module) {
// we need to find the lone mmdvmhost module
for (int i=0; i<3; i++) {
std::string test(mmdvm_path);
test.append(1, 'a'+i);
if (cfg.KeyExists(test)) {
cfg.GetValue(test, estr, type, 1, 16);
if (type.compare("mmdvmhost"))
continue; // this ain't it!
mmdvm_path.assign(test);
assigned_module = i;
break;
}
}
if (i >= 3) {
printf("mmdvm not defined in any module!\n");
return true;
}
RPTR_MOD = 'A' + i;
int repeater_module = i;
if (cfg.lookupValue(std::string(mmdvm_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) {
int l = value.length();
if (l<3 || l>CALL_SIZE-2) {
printf("Call '%s' is invalid length!\n", value.c_str());
if (0 > assigned_module) {
fprintf(stderr, "Error: no 'mmdvmhost' module found\n!");
return true;
} else {
for (i=0; i<l; i++) {
if (islower(value[i]))
value[i] = toupper(value[i]);
}
value.resize(CALL_SIZE, ' ');
}
strcpy(RPTR, value.c_str());
} else {
printf("%s.login is not defined!\n", mmdvm_path.c_str());
// make sure mmdvmhost module is defined
mmdvm_path.append(1, 'a' + assigned_module);
if (cfg.KeyExists(mmdvm_path)) {
cfg.GetValue(mmdvm_path, estr, type, 1, 16);
if (type.compare("mmdvmhost")) {
fprintf(stderr, "%s = %s is not 'mmdvmhost' type!\n", mmdvm_path.c_str(), type.c_str());
return true;
}
if (cfg.lookupValue("ircddb.login", value)) {
int l = value.length();
if (l<3 || l>CALL_SIZE-2) {
printf("Call '%s' is invalid length!\n", value.c_str());
return true;
} else {
for (i=0; i<l; i++) {
if (islower(value[i]))
value[i] = toupper(value[i]);
}
value.resize(CALL_SIZE, ' ');
}
strcpy(OWNER, value.c_str());
printf("ircddb.login = [%s]\n", OWNER);
} else {
printf("ircddb.login is not defined!\n");
fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module);
return true;
}
}
RPTR_MOD = 'A' + assigned_module;
if (GetValue(cfg, std::string(mmdvm_path+".internal_ip").c_str(), value, 7, IP_SIZE, "0.0.0.0")) {
MMDVM_IP = value;
} else
return true;
GetValue(cfg, "gateway.internal.port", i, 10000, 65535, 19000);
G2_IN_PORT = (unsigned short)i;
GetValue(cfg, std::string(mmdvm_path+".port").c_str(), i, 10000, 65535, 19998+repeater_module);
G2_OUT_PORT = (unsigned short)i;
GetValue(cfg, "mmdvm.local_port", i, 10000, 65535, 20011);
cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX);
cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX);
cfg.GetValue(mmdvm_path+"_internal_ip", type, MMDVM_IP, 7, IP_SIZE);
int i;
cfg.GetValue(mmdvm_path+"_local_port", type, i, 10000, 65535);
MMDVM_IN_PORT = (unsigned short)i;
GetValue(cfg, "mmdvm.gateway_port", i, 10000, 65535, 20010);
cfg.GetValue(mmdvm_path+"_gateway_port", type, i, 10000, 65535);
MMDVM_OUT_PORT = (unsigned short)i;
if (GetValue(cfg, "gateway.ip", value, 7, IP_SIZE, "127.0.0.1")) {
G2_INTERNAL_IP = value;
} else
return true;
GetValue(cfg, "timing.play.delay", DELAY_BETWEEN, 9, 25, 19);
GetValue(cfg, "timing.play.wait", DELAY_BEFORE, 1, 10, 2);
GetValue(cfg, std::string(mmdvm_path+".packet_wait").c_str(), WAIT_FOR_PACKETS, 6, 100, 25);
cfg.GetValue("log_qso", estr, log_qso);
return false;
}
@ -489,23 +414,47 @@ int main(int argc, const char **argv)
{
setbuf(stdout, NULL);
if (2 != argc) {
printf("usage: %s path_to_config_file\n", argv[0]);
printf(" %s --version\n", argv[0]);
fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]);
return 1;
}
if ('-' == argv[1][0]) {
printf("\nMMDVM Modem Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", MMDVM_VERSION);
printf("MMDVM Modem comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("\nQnetRelay Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", RELAY_VERSION);
printf("QnetRelay comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n");
return 0;
}
CQnetRelay qnmmdvm;
const char *qn = strstr(argv[0], "qnrelay");
if (NULL == qn) {
fprintf(stderr, "Error finding 'qnrelay' in %s!\n", argv[0]);
return 1;
}
qn += 7;
int module;
switch (*qn) {
case NULL:
module = -1;
break;
case 'a':
module = 0;
break;
case 'b':
module = 1;
break;
case 'c':
module = 2;
break;
default:
fprintf(stderr, "assigned module must be a, b or c\n");
return 1;
}
CQnetRelay qnmmdvm(module);
qnmmdvm.Run(argv[1]);
bool trouble = qnmmdvm.Run(argv[1]);
printf("%s is closing.\n", argv[0]);
return 0;
return trouble ? 1 : 0;
}

@ -20,11 +20,9 @@
#include <atomic>
#include <string>
#include <libconfig.h++>
#include <netinet/in.h>
using namespace libconfig;
#include "UnixDgramSocket.h"
#define CALL_SIZE 8
#define IP_SIZE 15
@ -33,9 +31,9 @@ class CQnetRelay
{
public:
// functions
CQnetRelay();
CQnetRelay(int mod);
~CQnetRelay();
void Run(const char *cfgfile);
bool Run(const char *cfgfile);
// data
static std::atomic<bool> keep_running;
@ -51,21 +49,21 @@ private:
// read configuration file
bool ReadConfig(const char *);
bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value);
bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value);
bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value);
bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value);
// Unix sockets
int assigned_module;
std::string gate2modem, modem2gate;
CUnixDgramWriter Modem2Gate;
CUnixDgramReader Gate2Modem;
// config data
char RPTR_MOD;
char RPTR[CALL_SIZE + 1];
char OWNER[CALL_SIZE + 1];
std::string MMDVM_IP, G2_INTERNAL_IP;
unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT;
int WAIT_FOR_PACKETS, DELAY_BEFORE, DELAY_BETWEEN;
std::string MMDVM_IP;
unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT;
bool log_qso;
// parameters
int msock, gsock;
int msock;
unsigned int seed;
unsigned short COUNTER;
};

@ -1,6 +1,6 @@
/*
* Copyright (C) 2010 by Scott Lawson KI4LKF
* Copyright (C) 2018 by Thomas A. Early N7TAE
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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
@ -32,23 +32,20 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <libconfig.h++>
#include <string>
#include "QnetTypeDefs.h"
#include "Random.h"
#include "QnetConfigure.h"
#include "UnixDgramSocket.h"
using namespace libconfig;
#define VERSION "v1.0"
int sockDst = -1;
struct sockaddr_in toDst;
#define VERSION "v2.2"
int module;
time_t tNow = 0;
short streamid_raw = 0;
bool isdefined[3] = { false, false, false };
std::string REPEATER, IP_ADDRESS;
int PORT, PLAY_WAIT, PLAY_DELAY;
std::string REPEATER, togateway;
int PLAY_WAIT, PLAY_DELAY;
unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 };
@ -72,152 +69,52 @@ unsigned short crc_tabccitt[256] = {
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78
};
void calcPFCS(unsigned char rawbytes[58])
{
unsigned short crc_dstar_ffff = 0xffff;
unsigned short tmp, short_c;
short int i;
for (i = 17; i < 56 ; i++) {
short_c = 0x00ff & (unsigned short)rawbytes[i];
tmp = (crc_dstar_ffff & 0x00ff) ^ short_c;
crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp];
}
crc_dstar_ffff = ~crc_dstar_ffff;
tmp = crc_dstar_ffff;
rawbytes[56] = (unsigned char)(crc_dstar_ffff & 0xff);
rawbytes[57] = (unsigned char)((tmp >> 8) & 0xff);
return;
}
CQnetConfigure cfg;
bool dst_open(const char *ip, const int port)
bool ReadCfgFile()
{
int reuse = 1;
const std::string estr;
std::string type;
std::string path = "module_";
path.append(1, 'a'+module);
sockDst = socket(PF_INET,SOCK_DGRAM,0);
if (sockDst == -1) {
printf("Failed to create DSTAR socket\n");
if (! cfg.KeyExists(path)) {
fprintf(stderr, "%s not defined!\n", path.c_str());
return true;
}
if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) {
close(sockDst);
sockDst = -1;
printf("setsockopt DSTAR REUSE failed\n");
cfg.GetValue(path, estr, type, 1, 16);
cfg.GetValue(path+"_callsign", type, REPEATER, 0, 6);
if (REPEATER.length() < 4) {
if (cfg.GetValue("ircddb_login", estr, REPEATER, 3, 6)) {
fprintf(stderr, "no Callsign for the repeater was found!\n");
return true;
}
memset(&toDst,0,sizeof(struct sockaddr_in));
toDst.sin_family = AF_INET;
toDst.sin_port = htons(port);
toDst.sin_addr.s_addr = inet_addr(ip);
fcntl(sockDst,F_SETFL,O_NONBLOCK);
return false;
}
void dst_close()
{
if (sockDst != -1) {
close(sockDst);
sockDst = -1;
}
return;
}
cfg.GetValue("gateway_modem2gate", estr, togateway, 1, FILENAME_MAX);
bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%d]\n", path, value);
return true;
}
bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%lg]\n", path, value);
return true;
}
bool get_value(const Config &cfg, const char *path, bool &value, bool default_value)
{
if (! cfg.lookupValue(path, value))
value = default_value;
printf("%s = [%s]\n", path, value ? "true" : "false");
return true;
}
bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value)
{
if (cfg.lookupValue(path, value)) {
int l = value.length();
if (l<min || l>max) {
printf("%s is invalid\n", path);
cfg.GetValue("timing_play_wait", estr, PLAY_WAIT, 1, 10);
cfg.GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25);
return false;
}
} else
value = default_value;
printf("%s = [%s]\n", path, value.c_str());
return true;
}
/* process configuration file */
bool read_config(const char *cfgFile)
void calcPFCS(unsigned char rawbytes[56])
{
Config cfg;
printf("Reading file %s\n", cfgFile);
// Read the file. If there is an error, report it and exit.
try {
cfg.readFile(cfgFile);
} catch(const FileIOException &fioex) {
printf("Can't read %s\n", cfgFile);
return true;
} catch(const ParseException &pex) {
printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError());
return true;
}
if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED"))
return true;
REPEATER.resize(6, ' ');
printf("REPEATER=[%s]\n", REPEATER.c_str());
unsigned short crc_dstar_ffff = 0xffff;
unsigned short tmp, short_c;
short int i;
for (short int m=0; m<3; m++) {
std::string path = "module.";
path += m + 'a';
std::string type;
if (cfg.lookupValue(std::string(path+".type").c_str(), type)) {
if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) {
printf("module type '%s' is invalid\n", type.c_str());
return true;
}
isdefined[m] = true;
}
}
if (false==isdefined[0] && false==isdefined[1] && false==isdefined[2]) {
printf("No repeaters defined!\n");
return true;
for (i = 15; i < 54 ; i++) {
short_c = 0x00ff & (unsigned short)rawbytes[i];
tmp = (crc_dstar_ffff & 0x00ff) ^ short_c;
crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp];
}
crc_dstar_ffff = ~crc_dstar_ffff;
tmp = crc_dstar_ffff;
if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1"))
return true;
get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, 19000);
get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2);
get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19);
return false;
rawbytes[54] = (unsigned char)(crc_dstar_ffff & 0xff);
rawbytes[55] = (unsigned char)((tmp >> 8) & 0xff);
return;
}
void ToUpper(std::string &str)
@ -229,21 +126,43 @@ void ToUpper(std::string &str)
int main(int argc, char *argv[])
{
unsigned short G2_COUNTER = 0;
if (argc != 4) {
printf("Usage: %s <module> <mycall> <yourcall>\n", argv[0]);
printf("Example: %s c n7tae xrf757cl\n", argv[0]);
printf("Where...\n");
printf(" c is the local repeater module\n");
printf(" n7tae is the value of mycall\n");
printf(" xrf757cl is the value of yourcall, in this case this is a Link command\n\n");
fprintf(stderr, "Usage: %s <module> <mycall> <yourcall>\n", argv[0]);
fprintf(stderr, "Example: %s c n7tae xrf757al\n", argv[0]);
fprintf(stderr, "Where...\n");
fprintf(stderr, " c is the local repeater module\n");
fprintf(stderr, " n7tae is the value of mycall\n");
fprintf(stderr, " xrf757al is the value of yourcall, in this case this is a Link command\n\n");
return 0;
}
switch (argv[1][0]) {
case '0':
case 'a':
case 'A':
module = 0;
break;
case '1':
case 'b':
case 'B':
module = 1;
break;
case '2':
case 'c':
case 'C':
module = 2;
break;
default:
fprintf(stderr, "module must be 0, a, A, 1, b, B, 2, c or C, not %s\n", argv[1]);
return 1;
}
std::string cfgfile(CFG_DIR);
cfgfile += "/qn.cfg";
if (read_config(cfgfile.c_str()))
if (cfg.Initialize(cfgfile.c_str()))
return 1;
if (ReadCfgFile())
return 1;
if (REPEATER.size() > 6) {
@ -252,14 +171,6 @@ int main(int argc, char *argv[])
}
ToUpper(REPEATER);
char module = argv[1][0];
if (islower(module))
module = toupper(module);
if ((module != 'A') && (module != 'B') && (module != 'C')) {
printf("module must be one of A B C\n");
return 1;
}
if (strlen(argv[2]) > 8) {
printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]);
return 1;
@ -283,127 +194,118 @@ int main(int argc, char *argv[])
RADIO_ID.resize(20, ' ');
time(&tNow);
srand(tNow + getpid());
if (dst_open(IP_ADDRESS.c_str(), PORT))
return 1;
SDSTR pkt;
memcpy(pkt.pkt_id,"DSTR", 4);
pkt.counter = htons(G2_COUNTER);
pkt.flag[0] = 0x73;
pkt.flag[1] = 0x12;
pkt.flag[2] = 0x00;
pkt.remaining = 0x30;
pkt.vpkt.icm_id = 0x20;
pkt.vpkt.dst_rptr_id = 0x00;
pkt.vpkt.snd_rptr_id = 0x01;
if (module == 'A')
pkt.vpkt.snd_term_id = 0x03;
else if (module == 'B')
pkt.vpkt.snd_term_id = 0x01;
else if (module == 'C')
pkt.vpkt.snd_term_id = 0x02;
CRandom Random;
CUnixDgramWriter ToGateway;
ToGateway.SetUp(togateway.c_str());
SDSVT pkt;
memcpy(pkt.title, "DSVT", 4);
pkt.config = 0x10U;
memset(pkt.flaga, 0U, 3U);
pkt.id = 0x20U;
pkt.flagb[0] = 0x0U;
pkt.flagb[1] = 0x1U;
if (module == 0)
pkt.flagb[2] = 0x3U;
else if (module == 1)
pkt.flagb[2] = 0x1U;
else if (module == 2)
pkt.flagb[2] = 0x2U;
else
pkt.vpkt.snd_term_id = 0x00;
streamid_raw = (unsigned short)(::rand() & 0xFFFF);
pkt.vpkt.streamid = htons(streamid_raw);
pkt.vpkt.ctrl = 0x80;
pkt.vpkt.hdr.flag[0] = pkt.vpkt.hdr.flag[1] = pkt.vpkt.hdr.flag[2] = 0x00;
pkt.flagb[3] = 0x0U;
streamid_raw = Random.NewStreamID();
pkt.streamid = htons(streamid_raw);
pkt.ctrl = 0x80;
pkt.hdr.flag[0] = pkt.hdr.flag[1] = pkt.hdr.flag[2] = 0x00;
REPEATER.resize(7, ' ');
memcpy(pkt.vpkt.hdr.r2, std::string(REPEATER + 'G').c_str(), 8);
memcpy(pkt.vpkt.hdr.r1, std::string(REPEATER + module).c_str(), 8);
memcpy(pkt.hdr.rpt2, REPEATER.c_str(), 8);
pkt.hdr.rpt2[7] = 'G';
memcpy(pkt.hdr.rpt1, REPEATER.c_str(), 8);
pkt.hdr.rpt1[7] = 'A' + module;
mycall.resize(8, ' ');
memcpy(pkt.vpkt.hdr.my, mycall.c_str(), 8);
memcpy(pkt.vpkt.hdr.nm, "QNET", 4);
memcpy(pkt.hdr.mycall, mycall.c_str(), 8);
memcpy(pkt.hdr.sfx, "QNET", 4);
if (yourcall.size() < 3)
yourcall = std::string(8-yourcall.size(), ' ') + yourcall; // right justify 1 or 2 letter commands
else
yourcall.resize(8, ' ');
memcpy(pkt.vpkt.hdr.ur, yourcall.c_str(), 8);
memcpy(pkt.hdr.urcall, yourcall.c_str(), 8);
calcPFCS(pkt.pkt_id);
calcPFCS(pkt.title);
// send the header
int sent = sendto(sockDst, pkt.pkt_id, 58, 0, (struct sockaddr *)&toDst, sizeof(toDst));
if (sent != 58) {
if (56 != ToGateway.Write(pkt.title, 56)) {
printf("%s: ERROR: Couldn't send header!\n", argv[0]);
dst_close();
return 1;
}
// prepare and send 10 voice packets
pkt.remaining = 0x13;
memcpy(pkt.vpkt.vasd.voice, silence, 9);
pkt.config = 0x20U;
memcpy(pkt.vasd.voice, silence, 9);
for (int i=0; i<10; i++) {
usleep(delay);
/* start sending silence + text */
pkt.counter = htons(++G2_COUNTER);
pkt.vpkt.ctrl = i;
pkt.ctrl = i;
switch (i) {
case 0: // sync voice frame
pkt.vpkt.vasd.text[0] = 0x55;
pkt.vpkt.vasd.text[1] = 0x2d;
pkt.vpkt.vasd.text[2] = 0x16;
pkt.vasd.text[0] = 0x55;
pkt.vasd.text[1] = 0x2d;
pkt.vasd.text[2] = 0x16;
break;
case 1:
pkt.vpkt.vasd.text[0] = '@' ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[0] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[1] ^ 0x93;
pkt.vasd.text[0] = '@' ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[0] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[1] ^ 0x93;
break;
case 2:
pkt.vpkt.vasd.text[0] = RADIO_ID[2] ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[3] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[4] ^ 0x93;
pkt.vasd.text[0] = RADIO_ID[2] ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[3] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[4] ^ 0x93;
break;
case 3:
pkt.vpkt.vasd.text[0] = 'A' ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[5] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[6] ^ 0x93;
pkt.vasd.text[0] = 'A' ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[5] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[6] ^ 0x93;
break;
case 4:
pkt.vpkt.vasd.text[0] = RADIO_ID[7] ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[8] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[9] ^ 0x93;
pkt.vasd.text[0] = RADIO_ID[7] ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[8] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[9] ^ 0x93;
break;
case 5:
pkt.vpkt.vasd.text[0] = 'B' ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[10] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[11] ^ 0x93;
pkt.vasd.text[0] = 'B' ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[10] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[11] ^ 0x93;
break;
case 6:
pkt.vpkt.vasd.text[0] = RADIO_ID[12] ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[13] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[14] ^ 0x93;
pkt.vasd.text[0] = RADIO_ID[12] ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[13] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[14] ^ 0x93;
break;
case 7:
pkt.vpkt.vasd.text[0] = 'C' ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[15] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[16] ^ 0x93;
pkt.vasd.text[0] = 'C' ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[15] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[16] ^ 0x93;
break;
case 8:
pkt.vpkt.vasd.text[0] = RADIO_ID[17] ^ 0x70;
pkt.vpkt.vasd.text[1] = RADIO_ID[18] ^ 0x4f;
pkt.vpkt.vasd.text[2] = RADIO_ID[19] ^ 0x93;
pkt.vasd.text[0] = RADIO_ID[17] ^ 0x70;
pkt.vasd.text[1] = RADIO_ID[18] ^ 0x4f;
pkt.vasd.text[2] = RADIO_ID[19] ^ 0x93;
break;
case 9: // terminal voice packet
pkt.vpkt.ctrl |= 0x40;
pkt.vpkt.vasd.text[0] = 0x70;
pkt.vpkt.vasd.text[1] = 0x4f;
pkt.vpkt.vasd.text[2] = 0x93;
pkt.ctrl |= 0x40;
pkt.vasd.text[0] = 0x70;
pkt.vasd.text[1] = 0x4f;
pkt.vasd.text[2] = 0x93;
break;
}
sent = sendto(sockDst,pkt.pkt_id, 29, 0, (struct sockaddr *)&toDst, sizeof(toDst));
if (sent != 29) {
if (27 != ToGateway.Write(pkt.title, 27)) {
printf("%s: ERROR: could not send voice packet %d\n", argv[0], i);
dst_close();
return 1;
}
usleep(delay);
}
dst_close();
return 0;
}

@ -1,6 +1,6 @@
#pragma once
/*
* Copyright 2017,2018 by Thomas Early, N7TAE
* Copyright 2017-2019 by Thomas Early, N7TAE
*
* 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
@ -18,8 +18,8 @@
*/
// for communicating with the g2 gateway on the internal port
#pragma pack(push, 1) // we need to be sure these structures don't have any dead space
typedef struct pkt_tag {
#pragma pack(push, 1) // used internally by Icom stacks
typedef struct dstr_tag {
unsigned char pkt_id[4]; // 0 "DSTR"
unsigned short counter; // 4
unsigned char flag[3]; // 6 { 0x73, 0x12, 0x00 }
@ -63,16 +63,16 @@ typedef struct pkt_tag {
} SDSTR;
#pragma pack(pop)
// for the g2 external port
// for the g2 external port and between QnetGateway programs
#pragma pack(push, 1)
typedef struct dsvt_tag {
unsigned char title[4]; // 0 "DSVT"
unsigned char config; // 4 0x10 is hdr 0x20 is vasd
unsigned char flaga[3]; // 5 zeros
unsigned char id; // 8 0x20
unsigned char flagb[3]; // 9 0x0 0x1 0x1
unsigned char flagb[3]; // 9 0x0 0x1 (A:0x3 B:0x1 C:0x2)
unsigned short streamid;// 12
unsigned char counter; // 14 hdr: 0x80 vsad: framecounter (mod 21)
unsigned char ctrl; // 14 hdr: 0x80 vsad: framecounter (mod 21)
union {
struct { // index
unsigned char flag[3]; // 15
@ -93,7 +93,7 @@ typedef struct dsvt_tag {
// for mmdvm
#pragma pack(push, 1)
typedef struct mmdvm_tag { // offset size
typedef struct dsrp_tag { // offset size
unsigned char title[4]; // "DSRP" 0
unsigned char tag; // Poll : 0xA 4
// Header : busy ? 0x22 : 0x20
@ -108,10 +108,10 @@ typedef struct mmdvm_tag { // offset size
// 0x01 Dstar Relay Unavailable
unsigned char r2[8]; // Repeater 2 11
unsigned char r1[8]; // Repeater 1 19
unsigned char yr[8]; // Your Call 27
unsigned char ur[8]; // Your Call 27
unsigned char my[8]; // My Call 35
unsigned char nm[4]; // Name 43
unsigned short pfcs; // checksum 47 49
unsigned char pfcs[2]; // checksum 47 49
} header;
struct {
unsigned short id; // random id number 5
@ -123,3 +123,10 @@ typedef struct mmdvm_tag { // offset size
};
} SDSRP;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct link_family_tag {
char title[4];
int family[3];
} SLINKFAMILY;
#pragma pack(pop)

@ -18,201 +18,42 @@
*/
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <string>
#include <libconfig.h++>
#include "QnetConfigure.h"
#include "QnetTypeDefs.h"
using namespace libconfig;
#define VERSION "v3.1"
int sockDst = -1;
struct sockaddr_in toDst;
FILE *fp = NULL;
time_t tNow = 0;
short streamid_raw = 0;
int moduleport[3] = { 0, 0, 0 };
std::string REPEATER, IP_ADDRESS;
int PORT, PLAY_WAIT, PLAY_DELAY;
unsigned short crc_tabccitt[256] = {
0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7,
0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876,
0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5,
0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974,
0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3,
0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72,
0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1,
0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70,
0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff,
0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e,
0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd,
0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c,
0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb,
0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a,
0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9,
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78
};
void calcPFCS(unsigned char rawbytes[58])
{
unsigned short crc_dstar_ffff = 0xffff;
unsigned short tmp, short_c;
short int i;
for (i = 17; i < 56 ; i++) {
short_c = 0x00ff & (unsigned short)rawbytes[i];
tmp = (crc_dstar_ffff & 0x00ff) ^ short_c;
crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp];
}
crc_dstar_ffff = ~crc_dstar_ffff;
tmp = crc_dstar_ffff;
rawbytes[56] = (unsigned char)(crc_dstar_ffff & 0xff);
rawbytes[57] = (unsigned char)((tmp >> 8) & 0xff);
return;
}
bool dst_open(const char *ip, const int port)
{
int reuse = 1;
sockDst = socket(PF_INET,SOCK_DGRAM,0);
if (sockDst == -1) {
printf("Failed to create DSTAR socket\n");
return true;
}
if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) {
close(sockDst);
sockDst = -1;
printf("setsockopt DSTAR REUSE failed\n");
return true;
}
memset(&toDst,0,sizeof(struct sockaddr_in));
toDst.sin_family = AF_INET;
toDst.sin_port = htons(port);
toDst.sin_addr.s_addr = inet_addr(ip);
fcntl(sockDst,F_SETFL,O_NONBLOCK);
return false;
}
void dst_close()
{
if (sockDst != -1) {
close(sockDst);
sockDst = -1;
}
return;
}
bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%d]\n", path, value);
return true;
}
bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
printf("%s = [%lg]\n", path, value);
return true;
}
bool get_value(const Config &cfg, const char *path, bool &value, bool default_value)
{
if (! cfg.lookupValue(path, value))
value = default_value;
printf("%s = [%s]\n", path, value ? "true" : "false");
return true;
}
bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value)
{
if (cfg.lookupValue(path, value)) {
int l = value.length();
if (l<min || l>max) {
printf("%s is invalid\n", path);
return false;
}
} else
value = default_value;
printf("%s = [%s]\n", path, value.c_str());
return true;
}
bool isamod[3] = { false, false, false };
std::string announce_dir;
std::string qnvoice_file;
/* process configuration file */
bool read_config(const char *cfgFile)
{
Config cfg;
CQnetConfigure cfg;
printf("Reading file %s\n", cfgFile);
// Read the file. If there is an error, report it and exit.
try {
cfg.readFile(cfgFile);
} catch(const FileIOException &fioex) {
printf("Can't read %s\n", cfgFile);
return true;
} catch(const ParseException &pex) {
printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError());
return true;
}
if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED"))
if (cfg.Initialize(cfgFile))
return true;
REPEATER.resize(6, ' ');
printf("REPEATER=[%s]\n", REPEATER.c_str());
for (short int m=0; m<3; m++) {
std::string path = "module.";
path += m + 'a';
for (int m=0; m<3; m++) {
std::string path("module_");
path.append(std::to_string(m));
std::string type;
if (cfg.lookupValue(std::string(path+".type").c_str(), type)) {
if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) {
if (cfg.KeyExists(path)) {
cfg.GetValue(path, "", type, 1, 16);
if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "itap")) {
printf("module type '%s' is invalid\n", type.c_str());
return true;
}
get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, 19998+m);
}
isamod[m] = true;
}
if (0==moduleport[0] && 0==moduleport[1] && 0==moduleport[2]) {
printf("No repeaters defined!\n");
return true;
}
if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1"))
return true;
get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2);
get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19);
std::string path("file_");
cfg.GetValue(path+"announce_dir", "", announce_dir, 2, FILENAME_MAX);
cfg.GetValue(path+"qnvoice_file", "", qnvoice_file, 2, FILENAME_MAX);
return false;
}
@ -226,35 +67,24 @@ void ToUpper(std::string &str)
int main(int argc, char *argv[])
{
unsigned short rlen = 0;
static unsigned short G2_COUNTER = 0;
size_t nread = 0;
SDSVT dsvt;
SDSTR dstr;
char RADIO_ID[21];
short int TEXT_idx = 0;
if (argc != 4) {
printf("Usage: %s <module> <mycall> <dvtoolFile>\n", argv[0]);
printf("Usage: %s <module> <datFile> <txtMsg>\n", argv[0]);
printf("Where...\n");
printf(" module is one of your modules\n");
printf(" mycall is your personal callsign\n");
printf(" dvtoolFile is a dvtool file\n");
printf(" <module> is one of your modules: A, B or C\n");
printf(" <datFile> is an installed voice file in the configured\n");
printf(" directory, for example \"unlinked.dat\"\n");
printf(" <txtMsg> is an up to 20-character text message\n");
return 0;
}
char module = argv[1][0];
std::string cfgfile(CFG_DIR);
cfgfile += "/qn.cfg";
if (read_config(cfgfile.c_str()))
return 1;
if (REPEATER.size() > 6) {
printf("repeaterCallsign can not be more than 6 characters, %s is invalid\n", REPEATER.c_str());
return 1;
}
ToUpper(REPEATER);
char module = argv[1][0];
if (islower(module))
module = toupper(module);
if ((module != 'A') && (module != 'B') && (module != 'C')) {
@ -262,176 +92,33 @@ int main(int argc, char *argv[])
return 1;
}
PORT = moduleport[module - 'A'];
if (0 == PORT) {
printf("module %c has no port defined!\n", module);
return 1;
}
if (strlen(argv[2]) > 8) {
printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]);
return 1;
}
std::string mycall(argv[2]);
ToUpper(mycall);
char pathname[FILENAME_MAX];
snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]);
fp = fopen(argv[3], "rb");
FILE *fp = fopen(pathname, "rb");
if (!fp) {
printf("Failed to open file %s for reading\n", argv[3]);
return 1;
}
/* DVTOOL + 4 byte num_of_records */
unsigned char buf[10];
nread = fread(buf, 10, 1, fp);
if (nread != 1) {
printf("Cant read first 10 bytes\n");
fclose(fp);
printf("Failed to find file %s for reading\n", pathname);
return 1;
}
if (0 != memcmp(buf, "DVTOOL", 6)) {
printf("DVTOOL signature not found in %s\n", argv[3]);
fclose(fp);
return 1;
}
memset(RADIO_ID, ' ', 20);
memset(RADIO_ID, '_', 20);
RADIO_ID[20] = '\0';
memcpy(RADIO_ID, "QnetVoice AMBE Data", 19);
unsigned long int delay = PLAY_DELAY * 1000L;
sleep(PLAY_WAIT);
time(&tNow);
srand(tNow + getpid());
unsigned int len = strlen(argv[3]);
strncpy(RADIO_ID, argv[3], len > 20 ? 20 : len);
for (int i=0; i<20; i++)
if (isspace(RADIO_ID[i]))
RADIO_ID[i] = '_';
if (dst_open(IP_ADDRESS.c_str(), PORT))
return 1;
// Read and reformat and write packets
while (true) {
/* 2 byte length */
nread = fread(&rlen, 2, 1, fp);
if (nread != 1) {
printf("End-Of-File\n");
break;
}
if (rlen == 56)
streamid_raw = (short)(::rand() & 0xFFFF);
else if (rlen == 27)
;
else {
printf("Wrong packet size!\n");
return 1;
}
/* read the packet */
nread = fread(dsvt.title, rlen, 1, fp);
printf("Read %d byte packet from %s\n", (int)nread*rlen, argv[3]);
if (rlen == 56)
printf("rpt1=%.8s rpt2=%.8s urcall=%.8s, mycall=%.8s, sfx=%.4s\n",
dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx);
else
printf("streamid=%04X counter=%02X\n", dsvt.streamid, dsvt.counter);
if (nread == 1) {
if (memcmp(dsvt.title, "DSVT", 4) != 0) {
printf("DVST title not found\n");
return 1;
}
if (dsvt.id != 0x20) {
printf("Not Voice type\n");
return 1;
}
if (dsvt.config!=0x10 && dsvt.config!=0x20) {
printf("Not a valid record type\n");
return 1;
}
dstr.counter = htons(G2_COUNTER++);
if (rlen == 56) {
memcpy(dstr.pkt_id, "DSTR", 4);
dstr.flag[0] = 0x73;
dstr.flag[1] = 0x12;
dstr.flag[2] = 0x00;
dstr.remaining = 0x30;
dstr.vpkt.icm_id = 0x20;
dstr.vpkt.dst_rptr_id = dsvt.flagb[0];
dstr.vpkt.snd_rptr_id = dsvt.flagb[1];
dstr.vpkt.snd_term_id = dsvt.flagb[2];
dstr.vpkt.streamid = htons(streamid_raw);
dstr.vpkt.ctrl = dsvt.counter;
for (int i=0; i<3; i++)
dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i];
memset(dstr.vpkt.hdr.r2, ' ', 36);
memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size());
dstr.vpkt.hdr.r1[7] = 'G';
memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size());
dstr.vpkt.hdr.r2[7] = module;
memcpy(dstr.vpkt.hdr.ur, "CQCQCQ", 6); /* yrcall */
memcpy(dstr.vpkt.hdr.my, mycall.c_str(), mycall.size());
memcpy(dstr.vpkt.hdr.nm, "QNET", 4);
calcPFCS(dstr.pkt_id);
} else {
dstr.remaining = 0x13;
dstr.vpkt.ctrl = dsvt.counter;
memcpy(dstr.vpkt.vasd.voice, dsvt.vasd.voice, 12);
if ((dstr.vpkt.vasd.text[0] != 0x55) || (dstr.vpkt.vasd.text[1] != 0x2d) || (dstr.vpkt.vasd.text[2] != 0x16)) {
if (TEXT_idx == 0) {
dstr.vpkt.vasd.text[0] = '@' ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 2) {
dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 5) {
dstr.vpkt.vasd.text[0] = 'A' ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 7) {
dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 10) {
dstr.vpkt.vasd.text[0] = 'B' ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 12) {
dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 15) {
dstr.vpkt.vasd.text[0] = 'C' ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
} else if (TEXT_idx == 17) {
dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70;
dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f;
dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93;
fp = fopen(qnvoice_file.c_str(), "w");
if (fp) {
fprintf(fp, "%c_%s_%s\n", module, argv[2], RADIO_ID);
fclose(fp);
} else {
dstr.vpkt.vasd.text[0] = 0x70;
dstr.vpkt.vasd.text[1] = 0x4f;
dstr.vpkt.vasd.text[2] = 0x93;
}
}
printf("Failed to open %s for writing", qnvoice_file.c_str());
return 1;
}
int sent = sendto(sockDst, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toDst, sizeof(toDst));
if (sent == 58)
printf("Sent DSTR HDR r2=%.8s r1=%.8s ur=%.8s my=%.8s nm=%.4s\n",
dstr.vpkt.hdr.r2, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm);
else if (sent == 29)
printf("Sent DSTR DATA streamid=%04X, ctrl=%02X\n", dstr.vpkt.streamid, dstr.vpkt.ctrl);
else
printf("ERROR: sendto returned %d!\n", sent);
}
usleep(delay);
}
dst_close();
fclose(fp);
return 0;
}

@ -1,11 +1,17 @@
QnetGateway
===========
The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle and the DVRPTR_V1. It is *incredibly easy* to build and install the system.
The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1. It is *incredibly easy* to build and install the system.
For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file.
QnetGateway is dual-stack capabile. This means it can simultaneously connect to rr.openquad.net, which is IPv4 based (using 32-bit internet addresses) and to rrv6.openquad.net which is IPv6 based (using 128-bit internet address). If your hot-spot/reapeater has IPv6 access you can enable dual-stack operation (it's IPv4-only by default) and then take advantage of direct world-routable address. The potential benefit of IPv6 to routing is significant.
To get started, clone the software to your Linux device:
The Qnet Gateway program includes support for Icom's new Terminal Mode and Access Point mode. For more information, Terminal Mode turns off the RF portion of you radio and just uses the AMBE vocoder to convert between audio and AMBE data and then sends and receives that data through a USB serial cable. Access Point mode turns your Icom radio into a high power, simplex hot-spot.
QnetGateway supports MMDVM modems directly, without the need for MMDVMHost. This is for hams that want to use their MMDVM devices and create a hot-spot for D-Star mode only. (You still can talk to your friends on other modes by gathering at multi-mode reflectors, like the QuadNet Array!)
For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the CONFIG+INSTALL file. To build QnetGateway for an Icom Repeater Stack, I have another repo at QnetIcomGateway. Detailed information is available there.
To get started with an MMDVM-modem, DVAP, DVRPTR or Icom Terminal and/or Access Point system, clone this software to your Linux device:
```
git clone git://github.com/n7tae/QnetGateway.git
@ -15,11 +21,12 @@ Then look to the MMDVM.README or the BUILDING file for more information.
QnetGateway includes a "remote control" program, called `qnremote`. After you build and install the system, type `qnremote` for a prompt on how to use it. Using this and cron, it's possible to setup schedules where you system will automatically link up to a reflector, or subscribe to a Routing Group. For More information, see DTMF+REMOTE.README.
For other details of interesting things QnetGatway can do, see the CONFIGURING file. For example, with QnetGateway, you can execute up to 36 different Linux scripts from you radio. Two scripts are include:
For other details of interesting things QnetGatway can do, see the OPERATING file. For example, with QnetGateway, you can execute up to 36 different Linux scripts from you radio. Two scripts are include:
```
YourCall = " HX" will halt your system.
YourCall = " RX" will reboot your system.
YourCall - " GX" will restart QnetGateway
```
QnetGateway 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. QnetGateway 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 LICENSE file for more details.

@ -0,0 +1,38 @@
/*
* Copyright (C) 2018-2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <unistd.h>
#include <cstdlib>
class CRandom
{
public:
CRandom() { srandom(getpid()); }
~CRandom() {}
unsigned short NewStreamID()
{
unsigned short r = 0;
while (0 == r)
r = 0xffffU & random();
return r;
}
};

@ -0,0 +1,28 @@
#pragma once
/*
* Copyright 2018-2019 by Thomas Early, N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
typedef struct echo_tag {
bool is_linked;
time_t last_time;
unsigned short streamid;
int fd;
char message[24];
SDSVT header; // only used in qnlink (qngateway writes the header to the file)
char file[FILENAME_MAX + 1];
} SECHO;

@ -0,0 +1,202 @@
#pragma once
/*
* Copyright (C) 2019 by Thomas Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <string>
class CSockAddress
{
public:
CSockAddress()
{
Clear();
}
CSockAddress(const struct sockaddr_storage &from)
{
Clear();
if (AF_INET == from.ss_family)
memcpy(&addr, &from, sizeof(struct sockaddr_in));
else
memcpy(&addr, &from, sizeof(struct sockaddr_in6));
}
CSockAddress(const int family, const unsigned short port, const char *address)
{
Clear();
if (AF_INET==family && address) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addr4->sin_family = AF_INET;
addr4->sin_port = htons(port);
if (0 == strncasecmp(address, "loc", 3))
inet_pton(AF_INET, "127.0.0.1", &(addr4->sin_addr));
else if (0 == strncasecmp(address, "any", 3))
inet_pton(AF_INET, "0.0.0.0", &(addr4->sin_addr));
else
inet_pton(AF_INET, address, &(addr4->sin_addr));
} else if (AF_INET6==family && address) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
addr6->sin6_family = AF_INET6;
addr6->sin6_port = htons(port);
if (0 == strncasecmp(address, "loc", 3))
inet_pton(AF_INET6, "::1", &(addr6->sin6_addr));
else if (0 == strncasecmp(address, "any", 3))
inet_pton(AF_INET6, "::", &(addr6->sin6_addr));
else
inet_pton(AF_INET6, address, &(addr6->sin6_addr));
} else if (AF_UNSPEC == family) {
memset(&addr, 0, sizeof(struct sockaddr_storage));
}
}
~CSockAddress() {}
void Initialize(int family, uint16_t port = 0U, const char *address = NULL)
{
Clear();
addr.ss_family = (sa_family_t)family;
if (AF_INET == family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addr4->sin_port = htons(port);
if (address) {
if (0 == strncasecmp(address, "loc", 3))
inet_pton(AF_INET, "127.0.0.1", &(addr4->sin_addr));
else if (0 == strncasecmp(address, "any", 3))
inet_pton(AF_INET, "0.0.0.0", &(addr4->sin_addr));
else
inet_pton(AF_INET, address, &(addr4->sin_addr));
}
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
addr6->sin6_port = htons(port);
if (address) {
if (0 == strncasecmp(address, "loc", 3))
inet_pton(AF_INET6, "::1", &(addr6->sin6_addr));
else if (0 == strncasecmp(address, "any", 3))
inet_pton(AF_INET6, "::", &(addr6->sin6_addr));
else
inet_pton(AF_INET6, address, &(addr6->sin6_addr));
}
}
}
CSockAddress &operator=(CSockAddress &from)
{
Clear();
if (AF_INET == from.addr.ss_family)
memcpy(&addr, &from, sizeof(struct sockaddr_in));
else
memcpy(&addr, &from, sizeof(struct sockaddr_in6));
return *this;
}
bool operator==(CSockAddress &from)
{
if (addr.ss_family == from.addr.ss_family) {
if (AF_INET == addr.ss_family) {
return (0==memcmp(&addr, &from, sizeof(struct sockaddr_in)));
} else {
return (0==memcmp(&addr, &from, sizeof(struct sockaddr_in6)));
}
} else
return false;
}
bool AddressIsZero()
{
if (AF_INET == addr.ss_family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
return (addr4->sin_addr.s_addr == 0U);
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
for (unsigned int i=0; i<16; i++) {
if (addr6->sin6_addr.s6_addr[i])
return false;
}
return true;
}
}
void ClearAddress()
{
if (AF_INET == addr.ss_family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addr4->sin_addr.s_addr = 0U;
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
memset(&(addr6->sin6_addr.s6_addr), 0, 16);
}
}
const char *GetAddress()
{
if (AF_INET == addr.ss_family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
if (NULL == inet_ntop(AF_INET, &(addr4->sin_addr), straddr, INET6_ADDRSTRLEN))
return "ERROR";
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
if (NULL == inet_ntop(AF_INET6, &(addr6->sin6_addr), straddr, INET6_ADDRSTRLEN))
return "ERROR";
}
return straddr;
}
int GetFamily()
{
return addr.ss_family;
}
unsigned short GetPort()
{
if (AF_INET == addr.ss_family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
return ntohs(addr4->sin_port);
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
return ntohs(addr6->sin6_port);
}
}
struct sockaddr *GetPointer()
{
return (struct sockaddr *)&addr;
}
size_t GetSize()
{
if (AF_INET == addr.ss_family)
return sizeof(struct sockaddr_in);
else
return sizeof(struct sockaddr_in6);
}
void Clear()
{
memset(&addr, 0, sizeof(struct sockaddr_storage));
}
private:
struct sockaddr_storage addr;
char straddr[INET6_ADDRSTRLEN];
};

@ -0,0 +1,219 @@
/*
* Copyright (C) 2010-2013 by Jonathan Naylor G4KLX
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "TCPReaderWriterClient.h"
#include <cstdio>
#include <cerrno>
#include <cassert>
#include <cstring>
CTCPReaderWriterClient::CTCPReaderWriterClient(const std::string &address, int family, const std::string &port) :
m_address(address),
m_family(family),
m_port(port),
m_fd(-1)
{
}
CTCPReaderWriterClient::CTCPReaderWriterClient() : m_fd(-1)
{
}
CTCPReaderWriterClient::~CTCPReaderWriterClient()
{
}
bool CTCPReaderWriterClient::Open(const std::string &address, int family, const std::string &port)
{
m_address = address;
m_family = family;
m_port = port;
return Open();
}
bool CTCPReaderWriterClient::Open()
{
if (m_fd != -1) {
fprintf(stderr, "ERROR: port for '%s' is already open!\n", m_address.c_str());
return true;
}
if (0 == m_address.size() || 0 == m_port.size() || 0 == std::stoul(m_port)) {
fprintf(stderr, "ERROR: '[%s]:%s' is malformed!\n", m_address.c_str(), m_port.c_str());
return true;
}
if (AF_INET!=m_family && AF_INET6!=m_family && AF_UNSPEC!=m_family) {
fprintf(stderr, "ERROR: family must be AF_INET, AF_INET6 or AF_UNSPEC\n");
return true;
}
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
//hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = IPPROTO_TCP;
struct addrinfo *res;
int s = EAI_AGAIN;
int count = 0;
while (EAI_AGAIN==s and count++<20) {
// connecting to a server, so we can wait until it's ready
s = getaddrinfo(m_address.c_str(), m_port.c_str(), &hints, &res);
if (s && s != EAI_AGAIN) {
fprintf(stderr, "ERROR: getaddrinfo of %s: %s\n", m_address.c_str(), gai_strerror(s));
return true;
}
std::this_thread::sleep_for(std::chrono::seconds(3));
}
if (EAI_AGAIN == s) {
fprintf(stderr, "ERROR getaddrinfo of %s failed 20 times\n", m_address.c_str());
return true;
}
struct addrinfo *rp;
for (rp = res; rp != NULL; rp = rp->ai_next) {
m_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (m_fd == -1)
continue;
if (connect(m_fd, rp->ai_addr, rp->ai_addrlen)) {
Close();
continue;
} else {
char buf[INET6_ADDRSTRLEN];
void *addr;
if (AF_INET == rp->ai_family) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)rp->ai_addr;
addr = &(addr4->sin_addr);
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)rp->ai_addr;
addr = &(addr6->sin6_addr);
}
if (inet_ntop(rp->ai_family, addr, buf, INET6_ADDRSTRLEN))
fprintf(stderr, "Successfully connected to %s at [%s]:%s\n", m_address.c_str(), buf, m_port.c_str());
break;
}
}
freeaddrinfo(res);
if (rp == NULL) {
fprintf(stderr, "Could not connect to any system returned by %s\n", m_address.c_str());
m_fd = -1;
return true;
}
return false;
}
int CTCPReaderWriterClient::ReadExact(unsigned char *buf, const unsigned int length)
{
unsigned int offset = 0U;
do {
int n = Read(buf + offset, length - offset);
if (n < 0)
return n;
offset += n;
} while ((length - offset) > 0U);
return length;
}
int CTCPReaderWriterClient::Read(unsigned char* buffer, const unsigned int length)
{
assert(buffer != NULL);
assert(length > 0U);
assert(m_fd != -1);
ssize_t len = recv(m_fd, buffer, length, 0);
if (len <= 0) {
if (len < 0)
fprintf(stderr, "Error returned from recv, err=%d\n", errno);
return -1;
}
return len;
}
int CTCPReaderWriterClient::ReadLine(std::string& line)
{
unsigned char c;
int resultCode;
int len = 0;
line = "";
do
{
resultCode = Read(&c, 1);
if(resultCode == 1) {
line += c;
len++;
}
} while(c != '\n' && resultCode == 1);
return resultCode <= 0 ? resultCode : len;
}
bool CTCPReaderWriterClient::Write(const unsigned char *buffer, const unsigned int length)
{
assert(buffer != NULL);
assert(length > 0U);
assert(m_fd != -1);
ssize_t ret = send(m_fd, (char *)buffer, length, 0);
if (ret != ssize_t(length)) {
if (ret < 0)
fprintf(stderr, "Error returned from send, err=%d\n", errno);
else
fprintf(stderr, "Error only wrote %d of %d bytes\n", int(ret), int(length));
return true;
}
return false;
}
bool CTCPReaderWriterClient::WriteLine(const std::string& line)
{
std::string lineCopy(line);
if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n')
lineCopy.append("\n");
size_t len = lineCopy.size();
bool result = true;
for(size_t i = 0; i < len && result; i++){
unsigned char c = lineCopy.at(i);
result = Write(&c , 1);
}
return result;
}
void CTCPReaderWriterClient::Close()
{
if (m_fd != -1) {
close(m_fd);
m_fd = -1;
}
}

@ -0,0 +1,59 @@
#pragma once
/* end of inma once */
/*
* Copyright (C) 2010,2011,2012,2013 by Jonathan Naylor G4KLX
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string>
#include <thread>
#include <chrono>
class CTCPReaderWriterClient {
public:
CTCPReaderWriterClient(const std::string &address, int family, const std::string &port);
CTCPReaderWriterClient();
~CTCPReaderWriterClient();
bool Open(const std::string &address, int family, const std::string &port);
bool Open();
int ReadExact(unsigned char *buffer, const unsigned int length);
int Read(unsigned char *buffer, const unsigned int length);
int ReadLine(std::string &line);
bool Write(const unsigned char* buffer, const unsigned int length);
bool WriteLine(const std::string &line);
int GetFD() { return m_fd; }
void Close();
private:
std::string m_address;
int m_family;
std::string m_port;
int m_fd;
};

@ -0,0 +1,38 @@
/*
* Copyright (C) 2019 by Thomas A. Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <ctime>
#include <chrono>
class CTimer
{
public:
CTimer() { start(); }
~CTimer() {}
void start() {
starttime = std::chrono::steady_clock::now();
}
double time() {
std::chrono::steady_clock::duration elapsed = std::chrono::steady_clock::now() - starttime;
return double(elapsed.count()) * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den;
}
private:
std::chrono::steady_clock::time_point starttime;
};

@ -0,0 +1,136 @@
/*
* Copyright (C) 2019 by Thomas Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <cstring>
#include <thread>
#include <chrono>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "UnixDgramSocket.h"
CUnixDgramReader::CUnixDgramReader() : fd(-1) {}
CUnixDgramReader::~CUnixDgramReader()
{
Close();
}
bool CUnixDgramReader::Open(const char *path) // returns true on failure
{
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0) {
fprintf(stderr, "CUnixDgramReader::Open: socket() failed: %s\n", strerror(errno));
return true;
}
//fcntl(fd, F_SETFL, O_NONBLOCK);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2);
int rval = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rval < 0) {
fprintf(stderr, "CUnixDgramReader::Open: bind() failed: %s\n", strerror(errno));
close(fd);
fd = -1;
return true;
}
return false;
}
ssize_t CUnixDgramReader::Read(void *buf, size_t size)
{
if (fd < 0)
return -1;
ssize_t len = read(fd, buf, size);
if (len < 1)
fprintf(stderr, "CUnixDgramReader::Read read() returned %d: %s\n", int(len), strerror(errno));
return len;
}
void CUnixDgramReader::Close()
{
if (fd >= 0)
close(fd);
fd = -1;
}
int CUnixDgramReader::GetFD()
{
return fd;
}
CUnixDgramWriter::CUnixDgramWriter() {}
CUnixDgramWriter::~CUnixDgramWriter() {}
void CUnixDgramWriter::SetUp(const char *path) // returns true on failure
{
// setup the socket address
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2);
}
ssize_t CUnixDgramWriter::Write(const void *buf, size_t size)
{
// open the socket
int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0) {
fprintf(stderr, "Failed to open socket %s : %s\n", addr.sun_path+1, strerror(errno));
return -1;
}
// connect to the receiver
int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rval < 0) {
fprintf(stderr, "Failed to connect to socket %s : %s\n", addr.sun_path+1, strerror(errno));
close(fd);
return -1;
}
ssize_t written = 0;
int count = 0;
while (written <= 0) {
written = write(fd, buf, size);
if (written == (ssize_t)size)
break;
else if (written < 0)
fprintf(stderr, "ERROR: faied to write to %s : %s\n", addr.sun_path+1, strerror(errno));
else if (written == 0)
fprintf(stderr, "Warning: zero bytes written to %s\n", addr.sun_path+1);
else if (written != (ssize_t)size) {
fprintf(stderr, "ERROR: only %d of %d bytes written to %s\n", (int)written, (int)size, addr.sun_path+1);
break;
}
if (++count >= 100) {
fprintf(stderr, "ERROR: Write failed after %d attempts\n", count-1);
break;
}
std::this_thread::sleep_for(std::chrono::microseconds(5));
}
close(fd);
return written;
}

@ -0,0 +1,45 @@
#pragma once
/*
* Copyright (C) 2019 by Thomas Early N7TAE
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include <sys/un.h>
class CUnixDgramReader
{
public:
CUnixDgramReader();
~CUnixDgramReader();
bool Open(const char *path);
ssize_t Read(void *buf, size_t size);
void Close();
int GetFD();
private:
int fd;
};
class CUnixDgramWriter
{
public:
CUnixDgramWriter();
~CUnixDgramWriter();
void SetUp(const char *path);
ssize_t Write(const void *buf, size_t size);
private:
struct sockaddr_un addr;
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,63 @@
A 0 29
B 30 32
C 63 34
D 98 32
E 131 26
F 158 31
G 190 36
H 227 31
I 259 28
J 288 36
K 325 28
L 354 28
M 383 34
N 418 32
O 451 29
P 481 32
Q 514 34
R 549 29
S 579 33
T 613 28
U 642 24
V 667 44
W 712 40
X 753 33
Y 787 31
Z 819 36
alpha 856 38
bravo 895 38
charlie 934 37
delta 972 37
echo 1010 33
foxtrot 1044 56
golf 1101 38
hotel 1140 39
india 1180 36
juliette 1217 39
kilo 1257 33
lima 1291 41
mike 1333 33
november 1367 38
oscar 1406 40
papa 1447 35
quebec 1483 36
romeo 1520 39
sierra 1560 35
tango 1596 40
uniform 1637 45
victor 1683 34
whiskey 1718 33
X-ray 1752 40
yankee 1793 39
zulu 1833 38
1 1872 34
2 1907 28
3 1936 37
4 1974 35
5 2010 37
6 2048 35
7 2084 38
8 2123 28
9 2152 37
0 2190 33

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -49,10 +49,8 @@ void CAPRS::SelectBand(short int rptr_idx, unsigned short streamID)
// Parameter buf is either:
// 12 bytes(packet from repeater was 29 bytes) or
// 15 bytes(packet from repeater was 32 bytes)
// Parameter len is either 12 or 15, because we took passed over the first 17 bytes
// in the repeater data
// Paramter seq is the byte at pos# 16(counting from zero) in the repeater data
void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len)
void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf)
{
unsigned char aprs_data[200];
char aprs_buf[1024];
@ -60,8 +58,6 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha
short int rptr_idx = -1;
len = len;
for (short int i = 0; i < 3; i++) {
if (streamID == aprs_streamID[i].streamID) {
rptr_idx = i;
@ -70,7 +66,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha
}
if ((rptr_idx < 0) || (rptr_idx > 2)) {
// printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx);
printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx);
return;
}
@ -93,7 +89,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha
if ((tnow - aprs_streamID[rptr_idx].last_time) < 30)
return;
if (aprs_sock == -1)
if (aprs_sock.GetFD() == -1)
return;
char *p = strchr((char*)aprs_data, ':');
@ -114,7 +110,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha
sprintf(aprs_buf, "%s,qAR,%s:%s\r\n", hdr, m_rptr->mod[rptr_idx].call.c_str(), aud);
// printf("GPS-A=%s", aprs_buf);
int rc = WriteSock(aprs_buf, strlen(aprs_buf));
int rc = aprs_sock.Write((unsigned char *)aprs_buf, strlen(aprs_buf));
if (rc == -1) {
if ((errno == EPIPE) ||
(errno == ECONNRESET) ||
@ -127,9 +123,8 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha
(errno == ENETUNREACH) ||
(errno == EHOSTDOWN) ||
(errno == ENOTCONN)) {
printf("CAPRS::ProcessText(): APRS_HOST closed connection,error=%d\n",errno);
close(aprs_sock);
aprs_sock = -1;
printf("CAPRS::ProcessText(): APRS_HOST closed connection, error=%d\n",errno);
aprs_sock.Close();
} else /* if it is WOULDBLOCK, we will not go into a loop here */
printf("CAPRS::ProcessText(): send error=%d\n", errno);
}
@ -156,23 +151,9 @@ void CAPRS::Init()
aprs_streamID[i].last_time = 0;
}
/* Initialize the APRS host */
memset(&aprs_addr,0,sizeof(struct sockaddr_in));
aprs_addr_len = sizeof(aprs_addr);
return;
}
int CAPRS::GetSock()
{
return aprs_sock;
}
void CAPRS::SetSock(int value)
{
aprs_sock = value;
}
bool CAPRS::WriteData(short int rptr_idx, unsigned char *data)
{
@ -256,90 +237,12 @@ unsigned int CAPRS::GetData(short int rptr_idx, unsigned char *data, unsigned in
void CAPRS::Open(const std::string OWNER)
{
fd_set fdset;
struct timeval tv;
short int MAX_WAIT = 15; /* 15 seconds wait time MAX */
int val = 1;
socklen_t val_len;
char snd_buf[512];
char rcv_buf[512];
bool ok = ResolveRmt(m_rptr->aprs.ip.c_str(), SOCK_STREAM, &aprs_addr);
if (!ok) {
printf("Can't resolve APRS_HOST %s\n", m_rptr->aprs.ip.c_str());
return;
}
/* fill it in */
aprs_addr.sin_family = AF_INET;
aprs_addr.sin_port = htons(m_rptr->aprs.port);
aprs_addr_len = sizeof(aprs_addr);
aprs_sock = socket(PF_INET, SOCK_STREAM, 0);
if (aprs_sock == -1) {
printf("Failed to create aprs socket,error=%d\n",errno);
return;
while (aprs_sock.Open(m_rptr->aprs.ip, AF_UNSPEC, std::to_string(m_rptr->aprs.port))) {
fprintf(stderr, "Failed to open %s, retry in 10 seconds...\n", m_rptr->aprs.ip.c_str());
std::this_thread::sleep_for(std::chrono::seconds(10));
}
fcntl(aprs_sock,F_SETFL,O_NONBLOCK);
val = 1;
if (setsockopt(aprs_sock,IPPROTO_TCP,TCP_NODELAY,(char *)&val, sizeof(val)) == -1) {
printf("setsockopt TCP_NODELAY TCP for aprs socket failed,error=%d\n",errno);
close(aprs_sock);
aprs_sock = -1;
return;
}
printf("Trying to connect to APRS...\n");
int rc = connect(aprs_sock, (struct sockaddr *)&aprs_addr, aprs_addr_len);
if (rc != 0) {
if (errno == EINPROGRESS) {
printf("Waiting for up to %d seconds for APRS_HOST\n", MAX_WAIT);
while (MAX_WAIT > 0) {
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(aprs_sock, &fdset);
rc = select(aprs_sock + 1, NULL, &fdset, NULL, &tv);
if (rc < 0) {
printf("Failed to connect to APRS...select,error=%d\n", errno);
close(aprs_sock);
aprs_sock = -1;
return;
} else if (rc == 0) { /* timeout */
MAX_WAIT--;
sleep(1);
} else {
val = 1; /* Assume it fails */
val_len = sizeof(val);
if (getsockopt(aprs_sock, SOL_SOCKET, SO_ERROR, (char *) &val, &val_len) < 0) {
printf("Failed to connect to APRS...getsockopt, error=%d\n", errno);
close(aprs_sock);
aprs_sock = -1;
return;
} else if (val == 0)
break;
MAX_WAIT--;
sleep(1);
}
}
if (MAX_WAIT == 0) {
printf("Failed to connect to APRS...timeout\n");
close(aprs_sock);
aprs_sock = -1;
return;
}
} else {
printf("Failed to connect to APRS, error=%d\n", errno);
close(aprs_sock);
aprs_sock = -1;
return;
}
}
printf("Connected to APRS %s:%d\n", m_rptr->aprs.ip.c_str(), m_rptr->aprs.port);
/* login to aprs */
sprintf(snd_buf, "user %s pass %d vers qngateway 2.99 UDP 5 ", OWNER.c_str(), m_rptr->aprs_hash);
@ -353,10 +256,10 @@ void CAPRS::Open(const std::string OWNER)
strcat(snd_buf, "\r\n");
while (true) {
rc = WriteSock(snd_buf, strlen(snd_buf));
int rc = aprs_sock.Write((unsigned char *)snd_buf, strlen(snd_buf));
if (rc < 0) {
if (errno == EWOULDBLOCK) {
recv(aprs_sock, rcv_buf, sizeof(rcv_buf), 0);
aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
printf("APRS login command failed, error=%d\n", errno);
@ -367,7 +270,7 @@ void CAPRS::Open(const std::string OWNER)
break;
}
}
recv(aprs_sock, rcv_buf, sizeof(rcv_buf), 0);
aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf));
return;
}
@ -495,55 +398,6 @@ unsigned int CAPRS::CalcCRC(unsigned char* buf, unsigned int len)
return (~my_crc & 0xffff);
}
ssize_t CAPRS::WriteSock(char *buffer, size_t n)
{
ssize_t num_written = 0;
size_t tot_written = 0;
char *buf = buffer;
for (tot_written = 0; tot_written < n;) {
num_written = write(aprs_sock, buf, n - tot_written);
if (num_written <= 0) {
if ((num_written == -1) && (errno == EINTR))
continue;
else
return num_written;
}
tot_written += num_written;
buf += num_written;
}
return tot_written;
}
bool CAPRS::ResolveRmt(const char *name, int type, struct sockaddr_in *addr)
{
struct addrinfo hints;
struct addrinfo *res;
struct addrinfo *rp;
bool found = false;
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = type;
int rc = getaddrinfo(name, NULL, &hints, &res);
if (rc != 0) {
printf("getaddrinfo return error code %d for [%s]\n", rc, name);
return false;
}
for (rp = res; rp != NULL; rp = rp->ai_next) {
if ((rp->ai_family == AF_INET) &&
(rp->ai_socktype == type)) {
memcpy(addr, rp->ai_addr, sizeof(struct sockaddr_in));
found = true;
break;
}
}
freeaddrinfo(res);
return found;
}
CAPRS::CAPRS(SRPTR *prptr)
{
m_rptr = prptr;
@ -551,4 +405,10 @@ CAPRS::CAPRS(SRPTR *prptr)
CAPRS::~CAPRS()
{
aprs_sock.Close();
}
void CAPRS::CloseSock()
{
aprs_sock.Close();
}

@ -23,6 +23,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include "TCPReaderWriterClient.h"
#include "IRCutils.h"
enum aprs_level { al_none, al_$1, al_$2, al_c1, al_r1, al_c2, al_csum1, al_csum2, al_csum3, al_csum4, al_data, al_end };
@ -47,7 +48,6 @@ typedef struct rptr_tag{
std::string band; /* 23cm ... */
double frequency, offset, latitude, longitude, range, agl;
std::string desc1, desc2, desc, url, package_version;
SPORTIP portip;
} mod[3];
} SRPTR;
@ -58,19 +58,14 @@ public:
~CAPRS();
SRPTR *m_rptr;
void SelectBand(short int rptr_idx, unsigned short streamID);
void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len);
ssize_t WriteSock(char *buffer, size_t n);
void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf);
void Open(const std::string OWNER);
void Init();
int GetSock();
void SetSock(int value);
void CloseSock();
CTCPReaderWriterClient aprs_sock;
private:
// data
// the aprs TCP socket
int aprs_sock = -1;
struct sockaddr_in aprs_addr;
socklen_t aprs_addr_len;
struct {
aprs_level al;
unsigned char data[300];
@ -93,5 +88,4 @@ private:
bool AddData(short int rptr_idx, unsigned char *data);
bool CheckData(short int rptr_idx);
unsigned int CalcCRC(unsigned char* buf, unsigned int len);
bool ResolveRmt(const char *name, int type, struct sockaddr_in *addr);
};

@ -0,0 +1,36 @@
# Copyright (c) 2019 by Thomas A. Early N7TAE
# copy this to ~/.bash_aliases if you don't want to use qnadmin to start and stop QnetGateway
function start () {
if [ $# == 1 ]; then
sudo make installbase && sudo make install${1} && sudo journalctl -u qn${1} -f
elif [ $# == 2 ]; then
sudo make installbase && sudo make install${1} && sudo journalctl -u qn${2} -f
else
echo "Usage: start module_name [watch_module]"
echo "Installs the base system and the module_name prefixed with 'qn' and tails the log."
echo "Use watch_module if you want to tail a different log"
echo "Only use this alias for systems with a single defined module."
echo "You must be in the QnetGateway build directory"
fi
}
function stop () {
if [ $# == 1 ]; then
sudo make uninstallbase && sudo make uninstall${1}
else
echo "usage: stop module_name"
echo "Uninstalls the base system and the module_name prefixed with 'qn'."
echo "Use this alias on for systems with a single defined module."
fi
}
function watch () {
if [ $# == 1 ]; then
sudo journalctl -u qn${1} -f
else
echo "usage: watch service_name"
echo "Tails the log from the service_name prefixed with 'qn'."
fi
}

@ -0,0 +1,211 @@
#
# Copyright (c) 2019 by Thomas A. Early N7TAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#########################################################################################################################
# #
# QnetGateway Default Parameter Values #
# #
#########################################################################################################################
# What follows need to also be valid bash shell variable definitions, therefore:
# No white space on either side of the equal sign (=)
# String values should be quoted if they contain any special chars, including white space
# If a string value is a simple word, it doesn't need to be quoted
# Use the single quote (') for quoting strings, not the double quote(")
# Comments can come after a key=value definition, introduced by a pound-sign (#)
#
# if a definition is commented out, it means that key has no default value. And it is
# include here just as a reference.
##########################################################################################################################
#
# IRCDDB - You MUST use a legal callsign for logging into any IRC network
#
#ircddb_login_d='' # login callsign for the ircDDB network
ircddb0_host_d='rr.openquad.net' # other irc networks include group1-irc.ircddb.net
ircddb0_port_d=9007 # not a good idea to change!
ircddb0_password_d='' # not needed for rr.openquad.net
ircddb1_host_d='' # second irc network
ircddb1_port_d=9007 # not a good idea to change!
ircddb1_password_d='' # not needed for rr.openquad.net
##########################################################################################################################
#
# GATEWAY
#
gateway_header_regen_d=true # regenerate headers from incoming data
gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server
gateway_ip_d='ANY_PORT' # the g2 port
gateway_port_d=40000 # don't change
gateway_ipv6_ip_d='ANY_PORT'
gateway_ipv6_port_d=9011 # IANA-approved DStar rouing port
gateway_gate2link_d='gate2link' # Unix sockets between qngateway and QnetLink
gateway_link2gate_d='link2gate' # all Unix sockets are on the file system, but hidden from view
gateway_modem2gate_d='modem2gate' # Unix Sockets between the modem(s) and the gateway (1 in, 3 out)
gateway_gate2modema_d='gate2modema'
gateway_gate2modemb_d='gate2modemb'
gateway_gate2modemc_d='gate2modemc'
gateway_latitude_d=0 # you can leave this unspecified for a mobile rig
gateway_longitude_d=0 # like the latitude
gateway_desc1_d='' # maximum of 20 characters, most special symbols are not allowed
gateway_desc2_d='' # just like desc1
gateway_url_d='github.com/n7tae/QnetGateway' # 80 characters max
gateway_find_route_d='' # CSV list of route(s) to load on boot-up (prevents the "not in cache" message)
##########################################################################################################################
#
# APRS - for tracking users and also this repeater.
#
aprs_enable_d=true # send info to APRS
aprs_host_d='rotate.aprs2.net' # the APRS network server
aprs_port_d=14580 # and port
aprs_interval_d=40 # keep-alive in minutes
aprs_filter_d='' # advanced feature
##########################################################################################################################
#
# LINK - controls the behavior of QnetLink (qnlink)
#
#link_admin_d='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt
#link_link_unlink_d='' # if defined, comma-separated list of users that can link and unlink a repeater
#link_no_link_unlink_d='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist
# if the blacklist is defined (even if it's empty), the link_unlink will not be read
link_incoming_ip_d='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection.
link_ref_port_d=20001 # port for REF linking, don't change
link_xrf_port_d=30001 # port for XRF linking, don't change
link_dcs_port_d=30051 # port for DCS linking, don't change
link_announce_d=true # do link, unlink, etc. announcements
link_acknowledge_d=true # send text acknowledgment on key-up
link_max_dongles_d=5 # maximum number of linked hot-spots
##########################################################################################################################
#
# GENERIC MODULE - These will be defined for any and all defined modules
#
module_x_link_at_start='' # For example, set to 'REF001 C' to link module to 1-charlie when the module starts.
module_x_auto_link=true # attempt to automatically re-link if the link should time out.
module_x_inactivity=0 # if no activity for this many minutes unlink reflector. Zero means no timer.
module_x_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty.
module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream
module_x_acknowledge=false # Do you want an ACK back?
module_x_ack_delay=250 # millisecond delay before acknowledgment
module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
module_x_agl=0 # the height above ground level for this repeater's antenna
##########################################################################################################################
#
# MMDVMHost - Special parameters when: module_x='mmdvmhost'
#
mmdvmhost_tx_frequency=0 # in MHz, not required, set in MMDVM.qn in Hz
mmdvmhost_rx_frequency=0 # in Mhz, not required, set in MMDVM.qn in Hz
mmdvmhost_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program
mmdvmhost_gateway_port=20010 # which port will QnetRelay be sending on
mmdvmhost_local_port=20011 # which port will MMDVMHost be sending on
##########################################################################################################################
#
# MMDVM Modem - Special parameters when: module_x='mmdmvmmodem'
#
mmdvmmodem_device='/dev/ttyAMA0' # where QnetModem will find the MMDVM modem
mmdvmmodem_tx_frequency=0 # in MHz, you MUST set a valid transmitter frequency!
mmdvmmodem_rx_frequency=0 # in MHz. If unset, then it's simplex, where rx=tx
mmdvmmodem_tx_offset=0 # in MHz. A frequency tweak.
mmdvmmodem_rx_offset=0 # in MHz. A frequency tweak.
mmdvmmodem_duplex=false # set to true for duplex for modems that support it
mmdvmmodem_rx_invert=false # receiver gate
mmdvmmodem_tx_invert=true # transmitter gate
mmdvmmodem_ptt_invert=false # push-to-talk gate
mmdvmmodem_tx_delay=100 # delay in milliseconds
mmdvmmodem_rx_level=128 # range is 0-255
mmdvmmodem_tx_level=128 # range is 0-255
##########################################################################################################################
#
# ITAP - Special parameters when: module_x='itap'
#
itap_device='/dev/ttyUSB0' # where the serial-to-USB cable show up
itap_frequency=0 # in MHz, not required for either mode, for AP mode, the simplex frequency is set on your radio
##########################################################################################################################
#
# DVAP - Special parameters when: module.x='dvap'
#
module_x_frequency=0 # in MHz, you must specify an operational frequency for the DVAP
module_x_offset=0 # it's a frequency tweak, in Hz
dvap_power=10 # TX power level: -12 to 10, 10 is maximum power
dvap_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best
dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case
##########################################################################################################################
#
# DVRPTR - Special parameters when: module_x='dvrptr'
#
# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr
dvrptr_tx_frequency=0 # in MHz, not required
dvrptr_rx_frequency=0 # in MHz, also not required
dvrptr_serial_number='00.00.00.00' # the DVRPTR serial number
dvrptr_rf_on='RFON' # put this in YRCALL to disable the channel
dvrptr_rf_off='RFOFF' # put this in YRCALL to enable the channel
dvrptr_rx_level=80 # see the DVRPTR V1 manual
dvrptr_duplex=false # set to true if the module is duplex
dvrptr_tx_delay=250 # milliseconds to allow for switching from rx to tx
dvrptr_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed
dvrptr_rx_invert=true # if your system isn't hearing you, try false
dvrptr_tx_invert=true # if you're not hearing your system, try false
##########################################################################################################################
#
# LOGGING - Control extra logging - useful for debugging
#
log_qso_d=false # QSO info goes into the log
log_irc_d=false # IRC debug info
log_dtmf_d=false # DTMF debug info
log_debug_d=false # WARNING, can produce a large number of log entries!
##########################################################################################################################
#
# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors
#
# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors!
# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail,
# even if QnetLink reports a successful authorization.
dplus_authorize_d=false # set to true if you want to use the closed-source DPlus reflectors and/or repeaters
dplus_ref_login_d='' # for logging into REF reflectors, if empty, ircddb_login will be used
dplus_use_reflectors_d=true # set to false if you are not going to link to DPlus reflectors
dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus repeaters
dplus_priority_d=true # set to true if you want DPlus reflector read after gwys.txt
##########################################################################################################################
#
# FILE - where important QnetGateway files and directories are found.
#
file_status_d='/usr/local/etc/rptr_status' # where repeater status info is passed between services
file_dtmf_d='/tmp' # where DTMF is decoded
file_echotest_d='/var/local' # echo dat files will end up here
file_qnvoice_file_d='/tmp/qnvoice.txt' # where qnvoice will create the play command
file_gwys_d='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is.
file_announce_dir_d='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements
##########################################################################################################################
#
# TIMINGS - for controlling how to deal with timing issues
#
# most users will not have to override any of these default values
timing_timeout_echo_d=1 # seconds before we assume echo has timed out
timing_timeout_voicemail_d=1 # seconds before we assume voicemail has timed out
timing_timeout_remote_g2_d=2 # after this many seconds with no packets, we assume the tx is closed
timing_timeout_local_rptr_d=1 # local repeater timeout, in seconds
timing_play_wait_d=1 # seconds before echo or voicemail playback occurs, between 1 and 10
timing_play_delay_d=19 # milliseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms

@ -0,0 +1,4 @@
#!/bin/sh
qnvoice ${2} gatewayrestart.dat 'Gateway Restart'
sleep 5
systemctl restart qngateway

@ -1,2 +1,4 @@
#!/bin/sh
halt
qnvoice ${2} shutdown.dat 'System Shutdown'
sleep 5
shutdown -h now

@ -1,2 +1,4 @@
#!/bin/sh
reboot
qnvoice ${2} rebooting.dat 'System Reboot'
sleep 5
shutdown -r now

@ -1,5 +0,0 @@
#/bin/bash
mv -f gwys.txt gwys.txt.bak
wget http://www.va3uv.com/gwys.txt

@ -1,30 +0,0 @@
#/bin/bash
# Get the big list from Ramesh (VA3UV) and extract the DCS, DStar and XReflectors only.
# Put XREF reflectors on port 20001 so they will use DPlus linking!
#
# 73
#
# Tom, n7tae (at) arrl (dot) net
if [ -e gwys.txt ]; then
mv -f gwys.txt gwys.txt.orig
fi
rm -f gwys.va2uv.txt
wget -nv -O gwys.va3uv.txt http://www.va3uv.com/gwys.txt
if [ -e gwys.va3uv.txt ]; then
echo "# from www.va3uv.com on `date`" > gwys.txt
echo "Got `awk '$1~/^REF/{print $1, $2, $3}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` REF reflectors"
# Move DPlus and DExtra to port 20001
echo "Got `awk '$1~/^XRF/{print $1, $2, 20001}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` XRF reflectors"
echo "Got `awk '$1~/^DCS/{print $1, $2, $3}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` DCS reflectors"
else
echo "Could not get gateways list from www.va3uv.com!"
if [ -e gwys.txt.orig ]; then
mv -f gwys.txt.orig gwys.txt
fi
fi

@ -23,6 +23,9 @@ public:
virtual void setSendQ(IRCMessageQueue *s) = 0;
virtual IRCMessageQueue *getSendQ(void) = 0;
virtual void putReplyMessage(IRCMessage *m) = 0;
virtual void sendPing(const std::string &to, const std::string &from) = 0;
virtual ~IRCApplication() {}
};

@ -7,11 +7,8 @@
#include "IRCClient.h"
#include "IRCutils.h"
#include <fcntl.h>
#include <errno.h>
IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password,
const std::string &versionInfo, const std::string &localAddr)
IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo)
{
safeStringCopy(host_name, hostName.c_str(), sizeof host_name);
@ -23,11 +20,6 @@ IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, con
this->app = app;
if (localAddr.empty())
safeStringCopy(local_addr, "0.0.0.0", sizeof local_addr);
else
safeStringCopy(local_addr, localAddr.c_str(), sizeof local_addr);
proto = new IRCProtocol(app, this->callsign, password, update_channel, versionInfo);
recvQ = NULL;
@ -58,26 +50,11 @@ void IRCClient::stopWork()
#define MAXIPV4ADDR 10
void IRCClient::Entry()
{
unsigned int numAddr;
struct sockaddr_in addr[MAXIPV4ADDR];
struct sockaddr_in myaddr;
CTCPReaderWriterClient ircSock;
int state = 0;
int timer = 0;
int sock = 0;
unsigned int currentAddr = 0;
numAddr = 0;
int result = getAllIPV4Addresses(local_addr, 0, &numAddr, &myaddr, 1);
if ((result != 0) || (numAddr != 1)) {
printf("IRCClient::Entry: local address not parseable, using 0.0.0.0\n");
memset(&myaddr, 0x00, sizeof(struct sockaddr_in));
}
socklen_t optlen;
while (true) {
@ -95,158 +72,26 @@ void IRCClient::Entry()
if (timer == 0) {
timer = 30;
if (getAllIPV4Addresses(host_name, port, &numAddr, addr, MAXIPV4ADDR) == 0) {
//printf("IRCClient::Entry: number of DNS entries %d\n", numAddr);
if (numAddr > 0) {
currentAddr = 0;
state = 1;
timer = 0;
}
}
}
break;
case 1:
if (terminateThread) {
printf("IRCClient::Entry: thread terminated at state=%d\n", state);
return;
}
if (timer == 0) {
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
printf("IRCClient::Entry: could not create socket!\n");
timer = 30;
state = 0;
} else {
if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
printf("IRCClient::Entry: fcntl error\n");
close(sock);
timer = 30;
state = 0;
}
else {
unsigned char *h = (unsigned char *) &(myaddr.sin_addr);
int res;
if ((h[0] != 0) || (h[1] != 0) || (h[2] != 0) || (h[3] != 0))
printf("IRCClient::Entry: bind: local address %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]);
res = bind(sock, (struct sockaddr *) &myaddr, sizeof (struct sockaddr_in));
if (res != 0) {
printf("IRCClient::Entry: bind error\n");
close(sock);
state = 0;
timer = 30;
break;
}
h = (unsigned char *) &(addr[currentAddr].sin_addr);
//printf("IRCClient::Entry: trying to connect to %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]);
res = connect(sock, (struct sockaddr *) (addr + currentAddr), sizeof (struct sockaddr_in));
if (res == 0) {
printf("IRCClient::Entry: connected to %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]);
if (! ircSock.Open(host_name, AF_UNSPEC, std::to_string(port))) {
state = 4;
} else {
if (errno == EINPROGRESS) {
//printf("IRCClient::Entry: connect in progress\n");
state = 3;
timer = 10; // 5 second timeout
} else {
printf("IRCClient::Entry: connect\n");
close(sock);
currentAddr++;
if (currentAddr >= numAddr) {
state = 0;
timer = 30;
} else {
state = 1;
timer = 4;
}
}
}
} // connect
timer = 0;
}
}
break;
case 3: {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
fd_set myset;
FD_ZERO(&myset);
FD_SET(sock, &myset);
int res = select(sock+1, NULL, &myset, NULL, &tv);
if (res < 0) {
printf("IRCClient::Entry: select\n");
close(sock);
state = 0;
timer = 30;
} else if (res > 0) { // connect is finished
socklen_t val_len;
int value;
val_len = sizeof value;
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &value, &val_len) < 0) {
printf("IRCClient::Entry: getsockopt error\n");
close(sock);
state = 0;
timer = 30;
} else {
if (value != 0) {
printf("IRCClient::Entry: SO_ERROR=%d\n", value);
close(sock);
currentAddr ++;
if (currentAddr >= numAddr) {
state = 0;
timer = 30;
} else {
state = 1;
timer = 2;
}
} else {
printf("IRCClient::Entry: connected2\n");
state = 4;
}
}
} else if (timer == 0) {
// select timeout and timer timeout
//printf("IRCClient::Entry: connect timeout\n");
close(sock);
currentAddr++;
if (currentAddr >= numAddr) {
state = 0;
timer = 30;
} else {
state = 1; // open new socket
timer = 2;
}
}
}
break;
case 4: {
case 4:
optlen = sizeof(int);
getsockopt(ircSock.GetFD(), SOL_SOCKET, SO_DOMAIN, &family, &optlen);
recvQ = new IRCMessageQueue();
sendQ = new IRCMessageQueue();
recv = new IRCReceiver(sock, recvQ);
recv = new IRCReceiver(&ircSock, recvQ);
recv->startWork();
proto->setNetworkReady(true);
state = 5;
timer = 0;
}
break;
@ -275,18 +120,12 @@ void IRCClient::Entry()
int len = strlen(buf);
if (buf[len - 1] == 10) { // is there a NL char at the end?
int r = send(sock, buf, len, 0);
if (r != len) {
printf("IRCClient::Entry: short write %d < %d\n", r, len);
if (ircSock.Write((unsigned char *)buf, len)) {
printf("IRCClient::Entry: short write\n");
timer = 0;
state = 6;
}
/* else
{
printf("write %d bytes (%s)\n", len, out.c_str());
} */
} else {
printf("IRCClient::Entry: no NL at end, len=%d\n", len);
@ -314,7 +153,7 @@ void IRCClient::Entry()
delete recvQ;
delete sendQ;
close(sock);
ircSock.Close();
if (terminateThread) { // request to end the thread
printf("IRCClient::Entry: thread terminated at state=%d\n", state);
@ -331,3 +170,8 @@ void IRCClient::Entry()
}
return;
}
int IRCClient::GetFamily()
{
return family;
}

@ -1,6 +1,8 @@
#pragma once
#include <future>
#include <atomic>
#include "../TCPReaderWriterClient.h"
#include "IRCReceiver.h"
#include "IRCMessageQueue.h"
@ -10,17 +12,19 @@
class IRCClient
{
public:
IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password,
const std::string &versionInfo, const std::string &localAddr);
IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo);
virtual ~IRCClient();
bool startWork();
void stopWork();
int GetFamily();
protected:
virtual void Entry();
private:
std::atomic<int> family;
std::future<void> client_thread;
char host_name[100];
char local_addr[100];
unsigned int port;
@ -33,7 +37,6 @@ private:
IRCMessageQueue *recvQ;
IRCMessageQueue *sendQ;
IRCProtocol *proto;
std::future<void> client_thread;
IRCApplication *app;
};

@ -9,8 +9,7 @@ struct CIRCDDBPrivate {
IRCDDBApp *app;
};
CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, const std::string &localAddr)
CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo)
: d(new CIRCDDBPrivate)
{
@ -18,7 +17,7 @@ CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::stri
d->app = new IRCDDBApp(update_channel);
d->client = new IRCClient(d->app, update_channel, hostName, port, callsign, password, versionInfo, localAddr);
d->client = new IRCClient(d->app, update_channel, hostName, port, callsign, password, versionInfo);
}
CIRCDDB::~CIRCDDB()
@ -28,11 +27,16 @@ CIRCDDB::~CIRCDDB()
delete d;
}
int CIRCDDB::GetFamily()
{
return d->client->GetFamily();
}
// A false return implies a network error, or unable to log in
bool CIRCDDB::open()
{
printf("start");
printf("starting CIRCDDB\n");
return d->client->startWork() && d->app->startWork();
}
@ -61,31 +65,30 @@ void CIRCDDB::kickWatchdog(const std::string &wdInfo)
}
// Send heard data, a false return implies a network error
bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1,
const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3)
bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3)
{
if (myCall.size() != 8) {
printf("CIRCDDB::sendHeard:myCall: len != 8");
printf("CIRCDDB::sendHeard:myCall: len != 8\n");
return false;
}
if (myCallExt.size() != 4) {
printf("CIRCDDB::sendHeard:myCallExt: len != 4");
printf("CIRCDDB::sendHeard:myCallExt: len != 4\n");
return false;
}
if (yourCall.size() != 8) {
printf("CIRCDDB::sendHeard:yourCall: len != 8");
printf("CIRCDDB::sendHeard:yourCall: len != 8\n");
return false;
}
if (rpt1.size() != 8) {
printf("CIRCDDB::sendHeard:rpt1: len != 8");
printf("CIRCDDB::sendHeard:rpt1: len != 8\n");
return false;
}
if (rpt2.size() != 8) {
printf("CIRCDDB::sendHeard:rpt2: len != 8");
printf("CIRCDDB::sendHeard:rpt2: len != 8\n");
return false;
}
@ -93,31 +96,30 @@ bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt,
}
// Send heard data, a false return implies a network error
bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1,
unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message)
bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message)
{
if (myCall.size() != 8) {
printf("CIRCDDB::sendHeard:myCall: len != 8");
printf("CIRCDDB::sendHeard:myCall: len != 8\n");
return false;
}
if (myCallExt.size() != 4) {
printf("CIRCDDB::sendHeard:myCallExt: len != 4");
printf("CIRCDDB::sendHeard:myCallExt: len != 4\n");
return false;
}
if (yourCall.size() != 8) {
printf("CIRCDDB::sendHeard:yourCall: len != 8");
printf("CIRCDDB::sendHeard:yourCall: len != 8\n");
return false;
}
if (rpt1.size() != 8) {
printf("CIRCDDB::sendHeard:rpt1: len != 8");
printf("CIRCDDB::sendHeard:rpt1: len != 8\n");
return false;
}
if (rpt2.size() != 8) {
printf("CIRCDDB::sendHeard:rpt2: len != 8");
printf("CIRCDDB::sendHeard:rpt2: len != 8\n");
return false;
}
@ -127,7 +129,7 @@ bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &m
dest = " ";
if (dest.size() != 8) {
printf("CIRCDDB::sendHeard:network_destination: len != 8");
printf("CIRCDDB::sendHeard:network_destination: len != 8\n");
return false;
}
@ -148,48 +150,46 @@ bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &m
return d->app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, dest, msg, "");
}
bool CIRCDDB::sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1,
unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors)
{
if (num_dv_frames<= 0 || num_dv_frames>65535) {
printf("CIRCDDB::sendHeard:num_dv_frames not in range 1-65535");
printf("CIRCDDB::sendHeard:num_dv_frames not in range 1-65535\n");
return false;
}
if (num_dv_silent_frames > num_dv_frames) {
printf("CIRCDDB::sendHeard:num_dv_silent_frames > num_dv_frames");
printf("CIRCDDB::sendHeard:num_dv_silent_frames > num_dv_frames\n");
return false;
}
if (num_bit_errors > 4*num_dv_frames) { // max 4 bit errors per frame
printf("CIRCDDB::sendHeard:num_bit_errors > (4*num_dv_frames)");
printf("CIRCDDB::sendHeard:num_bit_errors > (4*num_dv_frames)\n");
return false;
}
if (myCall.size() != 8) {
printf("CIRCDDB::sendHeard:myCall: len != 8");
printf("CIRCDDB::sendHeard:myCall: len != 8\n");
return false;
}
if (myCallExt.size() != 4) {
printf("CIRCDDB::sendHeard:myCallExt: len != 4");
printf("CIRCDDB::sendHeard:myCallExt: len != 4\n");
return false;
}
if (yourCall.size() != 8) {
printf("CIRCDDB::sendHeard:yourCall: len != 8");
printf("CIRCDDB::sendHeard:yourCall: len != 8\n");
return false;
}
if (rpt1.size() != 8) {
printf("CIRCDDB::sendHeard:rpt1: len != 8");
printf("CIRCDDB::sendHeard:rpt1: len != 8\n");
return false;
}
if (rpt2.size() != 8) {
printf("CIRCDDB::sendHeard:rpt2: len != 8");
printf("CIRCDDB::sendHeard:rpt2: len != 8\n");
return false;
}
@ -216,13 +216,11 @@ bool CIRCDDB::sendHeardWithTXStats(const std::string &myCall, const std::string
return d->app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, " ", "", stats);
}
// Send query for a gateway/reflector, a false return implies a network error
bool CIRCDDB::findGateway(const std::string &gatewayCallsign)
{
if (gatewayCallsign.size() != 8) {
printf("CIRCDDB::findGateway: len != 8");
printf("CIRCDDB::findGateway: len != 8\n");
return false;
}
std::string gcs = gatewayCallsign;
@ -230,11 +228,10 @@ bool CIRCDDB::findGateway(const std::string &gatewayCallsign)
return d->app->findGateway(gcs);
}
bool CIRCDDB::findRepeater(const std::string &repeaterCallsign)
{
if (repeaterCallsign.size() != 8) {
printf("CIRCDDB::findRepeater: len != 8");
printf("CIRCDDB::findRepeater: len != 8\n");
return false;
}
std::string rcs = repeaterCallsign;
@ -246,7 +243,7 @@ bool CIRCDDB::findRepeater(const std::string &repeaterCallsign)
bool CIRCDDB::findUser(const std::string &userCallsign)
{
if (userCallsign.size() != 8) {
printf("CIRCDDB::findUser: len != 8");
printf("CIRCDDB::findUser: len != 8\n");
return false;
}
std::string ucs = userCallsign;
@ -269,24 +266,24 @@ bool CIRCDDB::receiveRepeater(std::string &repeaterCallsign, std::string &gatewa
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_REPEATER) {
printf("CIRCDDB::receiveRepeater: unexpected response type");
printf("CIRCDDB::receiveRepeater: unexpected response type\n");
return false;
}
IRCMessage * m = d->app->getReplyMessage();
IRCMessage *m = d->app->getReplyMessage();
if (m == NULL) {
printf("CIRCDDB::receiveRepeater: no message");
printf("CIRCDDB::receiveRepeater: no message\n");
return false;
}
if (m->getCommand().compare("IDRT_REPEATER")) {
printf("CIRCDDB::receiveRepeater: wrong message type");
printf("CIRCDDB::receiveRepeater: wrong message type\n");
return false;
}
if (m->getParamCount() != 3) {
printf("CIRCDDB::receiveRepeater: unexpected number of message parameters");
printf("CIRCDDB::receiveRepeater: unexpected number of message parameters\n");
return false;
}
@ -306,24 +303,24 @@ bool CIRCDDB::receiveGateway(std::string &gatewayCallsign, std::string &address,
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_GATEWAY) {
printf("CIRCDDB::receiveGateway: unexpected response type");
printf("CIRCDDB::receiveGateway: unexpected response type\n");
return false;
}
IRCMessage * m = d->app->getReplyMessage();
IRCMessage *m = d->app->getReplyMessage();
if (m == NULL) {
printf("CIRCDDB::receiveGateway: no message");
printf("CIRCDDB::receiveGateway: no message\n");
return false;
}
if (m->getCommand().compare("IDRT_GATEWAY")) {
printf("CIRCDDB::receiveGateway: wrong message type");
printf("CIRCDDB::receiveGateway: wrong message type\n");
return false;
}
if (m->getParamCount() != 2) {
printf("CIRCDDB::receiveGateway: unexpected number of message parameters");
printf("CIRCDDB::receiveGateway: unexpected number of message parameters\n");
return false;
}
@ -343,30 +340,29 @@ bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsi
return receiveUser(userCallsign, repeaterCallsign, gatewayCallsign, address, dummy);
}
bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address,
std::string &timeStamp)
bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address, std::string &timeStamp)
{
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_USER) {
printf("CIRCDDB::receiveUser: unexpected response type");
printf("CIRCDDB::receiveUser: unexpected response type\n");
return false;
}
IRCMessage * m = d->app->getReplyMessage();
IRCMessage *m = d->app->getReplyMessage();
if (m == NULL) {
printf("CIRCDDB::receiveUser: no message");
printf("CIRCDDB::receiveUser: no message\n");
return false;
}
if (m->getCommand().compare("IDRT_USER")) {
printf("CIRCDDB::receiveUser: wrong message type");
printf("CIRCDDB::receiveUser: wrong message type\n");
return false;
}
if (m->getParamCount() != 5) {
printf("CIRCDDB::receiveUser: unexpected number of message parameters");
printf("CIRCDDB::receiveUser: unexpected number of message parameters\n");
return false;
}
@ -381,9 +377,46 @@ bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsi
return true;
}
bool CIRCDDB::receivePing(std::string &repeaterCallsign)
{
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_PING) {
printf("CIRCDDB::receivePing: unexpected response type\n");
return false;
}
IRCMessage *m = d->app->getReplyMessage();
if (NULL == m) {
printf("CIRCDDB::receivePing: no message\n");
return false;
}
if (m->getCommand().compare("IDRT_PING")) {
printf("CIRCDDB::receivePing: wrong messsage type\n");
return false;
}
if (1 != m->getParamCount()) {
printf("CIRCDDB::receivePing: unexpected number of message parameters\n");
return false;
}
repeaterCallsign = m->getParam(0);
delete m;
return true;
}
void CIRCDDB::sendPing(const std::string &to, const std::string &from)
{
d->app->sendPing(to, from);
}
void CIRCDDB::close() // Implictely kills any threads in the IRC code
{
d->client->stopWork();
d->app->stopWork();
}

@ -6,7 +6,8 @@ enum IRCDDB_RESPONSE_TYPE {
IDRT_NONE,
IDRT_USER,
IDRT_GATEWAY,
IDRT_REPEATER
IDRT_REPEATER,
IDRT_PING
};
enum DSTAR_PROTOCOL {
@ -15,19 +16,20 @@ enum DSTAR_PROTOCOL {
DP_DPLUS
};
struct CIRCDDBPrivate;
class CIRCDDB
{
public:
CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, const std::string &localAddr = "");
CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo);
~CIRCDDB();
// returns the socket family type
int GetFamily();
// A false return implies a network error, or unable to log in
bool open();
// rptrQTH can be called multiple times if necessary
// rptrcall callsign of the repeater
// latitude WGS84 position of antenna in degrees, positive value -> NORTH
@ -36,8 +38,6 @@ public:
void rptrQTH(const std::string &rptrcall, double latitude, double longitude, const std::string &desc1, const std::string &desc2, const std::string &infoURL, const std::string &swVersion);
// rptrQRG can be called multiple times if necessary
// module letter of the module, valid values: "A", "B", "C", "D", "AD", "BD", "CD", "DD"
// txFrequency repeater TX frequency in MHz
@ -47,7 +47,6 @@ public:
void rptrQRG(const std::string &rptrcall, double txFrequency, double duplexShift, double range, double agl);
// If you call this method once, watchdog messages will be sent to the
// to the ircDDB network every 15 minutes. Invoke this method every 1-2 minutes to indicate
// that the gateway is working properly. After activating the watchdog, a red LED will be displayed
@ -58,8 +57,6 @@ public:
void kickWatchdog(const std::string &wdInfo);
// get internal network status
int getConnectionState();
// one of these values is returned:
@ -72,14 +69,12 @@ public:
// Send heard data, a false return implies a network error
bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3);
// same as sendHeard with two new fields:
// network_destination: empty string or 8-char call sign of the repeater
// or reflector, where this transmission is relayed to.
// tx_message: 20-char TX message or empty string, if the user did not
// send a TX message
bool sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1,
unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message);
bool sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message);
// this method should be called at the end of a transmission
// num_dv_frames: number of DV frames sent out (96 bit frames, 20ms)
@ -91,8 +86,7 @@ public:
// So, the overall bit error rate is calculated like this:
// BER = num_bit_errors / (num_dv_frames * 24)
// Set num_bit_errors = -1, if the error information is not available.
bool sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1,
unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors);
bool sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors);
// The following three functions don't block waiting for a reply, they just send the data
@ -124,10 +118,12 @@ public:
bool receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address, std::string &timeStamp);
void close(); // Implictely kills any threads in the IRC code
bool receivePing(std::string &repeaterCallsign);
void sendPing(const std::string &to, const std::string &from);
void close(); // Implictely kills any threads in the IRC code
private:
struct CIRCDDBPrivate * const d;
};

@ -253,6 +253,8 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType()
return IDRT_REPEATER;
} else if (msgType == std::string("IDRT_GATEWAY")) {
return IDRT_GATEWAY;
} else if (msgType == std::string("IDRT_PING")) {
return IDRT_PING;
}
printf("IRCDDBApp::getMessageType: unknown msg type: %s\n", msgType.c_str());
@ -265,6 +267,11 @@ IRCMessage *IRCDDBApp::getReplyMessage()
return d->replyQ.getMessage();
}
void IRCDDBApp::putReplyMessage(IRCMessage *m)
{
d->replyQ.putMessage(m);
}
bool IRCDDBApp::startWork()
{
d->terminateThread = false;
@ -428,6 +435,37 @@ void IRCDDBApp::userChanOp(const std::string &nick, bool op)
d->userMapMutex.unlock();
}
void IRCDDBApp::sendPing(const std::string &to, const std::string &from)
{
std::string t = to.substr(0, 7);
ReplaceChar(t, '_', ' ');
while (isspace(t[t.length()-1]))
t.pop_back();
ToLower(t);
d->userMapMutex.lock();
for (int j=1; j <= 4; j++) {
std::string ircUser = t + std::string("-") + std::to_string(j);
if (1 == d->user.count(ircUser)) {
std::string f(from);
ReplaceChar(f, ' ', '_');
IRCMessage *rm = new IRCMessage(ircUser, "IDRT_PING");
rm->addParam(f);
std::string out;
rm->composeMessage(out);
out.pop_back();
out.pop_back();
printf("IRCDDBApp::sendPing: %s\n", out.c_str());
d->sendQ->putMessage(rm);
break;
}
}
d->userMapMutex.unlock();
}
static const int numberOfTables = 2;
std::string IRCDDBApp::getIPAddress(std::string &zonerp_cs)
@ -472,48 +510,8 @@ bool IRCDDBApp::findGateway(const std::string &gwCall)
return true;
}
static void findReflector(const std::string &rptrCall, IRCDDBAppPrivate *d)
{
std::string zonerp_cs;
std::string ipAddr;
#define MAXIPV4ADDR 5
struct sockaddr_in addr[MAXIPV4ADDR];
unsigned int numAddr = 0;
char host_name[80];
std::string host = rptrCall.substr(0,6) + ".reflector.ircddb.net";
safeStringCopy(host_name, host.c_str(), sizeof host_name);
if (getAllIPV4Addresses(host_name, 0, &numAddr, addr, MAXIPV4ADDR) == 0) {
if (numAddr > 0) {
unsigned char *a = (unsigned char *) &addr[0].sin_addr;
char buf[16];
snprintf(buf, 16, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]);
ipAddr = buf;
zonerp_cs = rptrCall;
zonerp_cs[7] = 'G';
}
}
IRCMessage *m2 = new IRCMessage("IDRT_REPEATER");
m2->addParam(rptrCall);
m2->addParam(zonerp_cs);
m2->addParam(ipAddr);
d->replyQ.putMessage(m2);
}
bool IRCDDBApp::findRepeater(const std::string &rptrCall)
{
if (0==rptrCall.compare(0, 3, "XRF") || 0==rptrCall.compare(0, 3, "REF")) {
findReflector(rptrCall, d);
return true;
}
std::string arearp_cs = rptrCall;
ReplaceChar(arearp_cs, ' ', '_');

@ -33,6 +33,9 @@ public:
virtual void setSendQ(IRCMessageQueue *s);
virtual IRCMessageQueue *getSendQ();
virtual void putReplyMessage(IRCMessage *m);
virtual void sendPing(const std::string &to, const std::string &from);
bool startWork();
void stopWork();
@ -44,9 +47,7 @@ public:
bool findRepeater(const std::string &s);
bool findGateway(const std::string &s);
bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1,
const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3,
const std::string &destination, const std::string &tx_msg, const std::string &tx_stats);
bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &destination, const std::string &tx_msg, const std::string &tx_stats);
int getConnectionState();

@ -10,6 +10,7 @@
IRCProtocol::IRCProtocol(IRCApplication *app, const std::string &callsign, const std::string &password, const std::string &channel, const std::string &versionInfo)
{
srand(time(NULL));
this->password = password;
this->channel = channel;
this->app = app;
@ -175,13 +176,24 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ)
}
}
} else if (0 == m->command.compare("PRIVMSG")) {
if ((m->numParams == 2) && (app != NULL)) {
if (app) {
std::string out;
m->composeMessage(out);
out.pop_back(); out.pop_back();
if (2 == m->numParams) {
if (0 == m->params[0].compare(channel)) {
app->msgChannel(m);
} else if (0 == m->params[0].compare(currentNick)) {
if (0 == m->params[1].find("IDRT_PING")) {
std::string from = m->params[1].substr(10);
IRCMessage *rm = new IRCMessage("IDRT_PING");
rm->addParam(from);
app->putReplyMessage(rm);
} else
app->msgQuery(m);
}
}
}
} else if (0 == m->command.compare("352")) { // WHO list
if ((m->numParams >= 7) && 0==m->params[0].compare(currentNick) && 0==m->params[1].compare(channel)) {
if (app != NULL) {
@ -342,5 +354,3 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ)
return true;
}

@ -6,7 +6,7 @@
#include "IRCMessage.h"
#include "IRCReceiver.h"
static int doRead(int sock, char * buf, int buf_size)
static int doRead(CTCPReaderWriterClient *ircSock, char *buf, int buf_size)
{
struct timeval tv;
tv.tv_sec = 1;
@ -14,26 +14,27 @@ static int doRead(int sock, char * buf, int buf_size)
fd_set rdset;
fd_set errset;
int fd = ircSock->GetFD();
FD_ZERO(&rdset);
FD_ZERO(&errset);
FD_SET(sock, &rdset);
FD_SET(sock, &errset);
FD_SET(fd, &rdset);
FD_SET(fd, &errset);
int res;
res = select(sock+1, &rdset, NULL, &errset, &tv);
res = select(fd+1, &rdset, NULL, &errset, &tv);
if ( res < 0 ) {
printf("IRCReceiver::doread: select() error.\n");
return -1;
} else if ( res > 0 ) {
if (FD_ISSET(sock, &errset)) {
if (FD_ISSET(fd, &errset)) {
printf("IRCReceiver::doRead: FD_ISSET error\n");
return -1;
}
if (FD_ISSET(sock, &rdset)) {
res = recv(sock, buf, buf_size, 0);
if (FD_ISSET(fd, &rdset)) {
res = ircSock->Read((unsigned char *)buf, buf_size);
if (res < 0) {
printf("IRCReceiver::doRead: recv error\n");
@ -59,7 +60,7 @@ void IRCReceiver::Entry()
while (!terminateThread) {
char buf[200];
int r = doRead(sock, buf, sizeof buf);
int r = doRead(ircSock, buf, sizeof buf);
if (r < 0) {
recvQ->signalEOF();
@ -133,9 +134,9 @@ void IRCReceiver::Entry()
return;
}
IRCReceiver::IRCReceiver(int sock, IRCMessageQueue *q)
IRCReceiver::IRCReceiver(CTCPReaderWriterClient *sock, IRCMessageQueue *q)
{
this->sock = sock;
ircSock = sock;
recvQ = q;
}

@ -1,11 +1,12 @@
#pragma once
#include <future>
#include "IRCMessageQueue.h"
#include "../TCPReaderWriterClient.h"
class IRCReceiver
{
public:
IRCReceiver(int sock, IRCMessageQueue *q);
IRCReceiver(CTCPReaderWriterClient *ircSock, IRCMessageQueue *q);
virtual ~IRCReceiver();
bool startWork();
void stopWork();
@ -14,6 +15,7 @@ protected:
virtual void Entry();
private:
CTCPReaderWriterClient *ircSock;
bool terminateThread;
int sock;
IRCMessageQueue *recvQ;

@ -35,84 +35,6 @@ std::vector<std::string> stringTokenizer(const std::string &s)
return result;
}
int getAllIPV4Addresses(const char * name, unsigned short port, unsigned int * num, struct sockaddr_in * addr, unsigned int max_addr)
{
struct addrinfo hints;
struct addrinfo * res;
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int r = getaddrinfo(name, NULL, &hints, &res);
if (r == 0) {
struct addrinfo * rp;
unsigned int numAddr = 0;
for (rp = res; rp != NULL; rp = rp->ai_next) {
if (rp->ai_family == AF_INET)
numAddr ++;
}
if (numAddr > 0) {
if (numAddr > max_addr)
numAddr = max_addr;
int * shuffle = new int[numAddr];
unsigned int i;
for (i=0; i < numAddr; i++)
shuffle[i] = i;
for (i=0; i < (numAddr - 1); i++) {
if (rand() & 1) {
int tmp;
tmp = shuffle[i];
shuffle[i] = shuffle[i+1];
shuffle[i+1] = tmp;
}
}
for (i=(numAddr - 1); i > 0; i--) {
if (rand() & 1) {
int tmp;
tmp = shuffle[i];
shuffle[i] = shuffle[i-1];
shuffle[i-1] = tmp;
}
}
for (rp = res, i=0 ; (rp != NULL) && (i < numAddr); rp = rp->ai_next) {
if (rp->ai_family == AF_INET) {
memcpy( addr+shuffle[i], rp->ai_addr, sizeof (struct sockaddr_in) );
addr[shuffle[i]].sin_port = htons(port);
i++;
}
}
delete[] shuffle;
}
*num = numAddr;
freeaddrinfo(res);
return 0;
} else {
printf("getaddrinfo: %s\n", gai_strerror(r));
return 1;
}
}
void safeStringCopy (char *dest, const char *src, unsigned int buf_size)
{
unsigned int i = 0;

@ -27,8 +27,6 @@ time_t parseTime(const std::string str);
std::vector<std::string> stringTokenizer(const std::string &str);
int getAllIPV4Addresses(const char *name, unsigned short port, unsigned int *num, struct sockaddr_in *addr, unsigned int max_addr);
void safeStringCopy(char * dest, const char * src, unsigned int buf_size);
char *getCurrentTime(void);

@ -1,32 +1,9 @@
# g2_ircddb Configuration for me
# A Simple Configuration for a 2M DVAP
ircddb = {
login = "XX0XXX"
# If you are not using rr.openquad.net, you need to specify the host and possibly the password.
#
# host = "some.server.host" // others include group1-irc.ircddb.net
# password = "1111111111111" // not needed for rr.openquad.net
}
ircddb_login='Q1ABC'
module = {
c = { // change the module to "b" if you have a 70cm DVAP
type = "dvap"
serial_number = "AP123456" // your serial number is visible through the case
frequency = 145.5 // this is the default value, chose a quiet frequency
# uncomment and set if you want the following to appear on you ircddb host website.
# range = 0.0 // in meters (1609.344 is one mile)
# agl = 0.0 // in meters
# latitude = 0.000000 // north is positive
# longitude = 0.000000 // east is positive
# desc1 = "Location1" // up to 20 chars
# desc2 = "location2" // up to 20 chars
}
}
module_c='dvap' # change to 'module_b' if you have a 70cm dvap
module_c_serial_number='AP123456' # your serial number is visible through the case
module_c_frequency=146.5 # in MHz, chose a quiet frequency
glink = {
# add the callsigns that can shutdown or reboot your system
# admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts
# link to the reflector of your choice. the first character is the module you are linking.
# link_at_start = "CREF001C"
}
#dplus_authorize=true # uncomment if you want to link to the legacy D-Plus system

@ -1,310 +1,239 @@
# g2_ircddb Configuration
ircddb = {
login = "CHANGEME!!!!"; # login callsign for the ircDDB network
# host = "rr.openquad.net" # other include group1-irc.ircddb.net
# port = 9007 # not a good idea to change!
# password = "1111111111111" # not needed for Openquad
}
gateway = {
# regen_header = true # regenerate headers from incoming data
# send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server
# local_irc_ip = "0.0.0.0" # 0.0.0.0 means accept any incoming connections
# aprs_send = true # send info to aprs
# ip = "127.0.0.1" # where the gateway is running
external = {
# ip = "0.0.0.0"
# port = 40000
}
internal = {
# ip = "0.0.0.0"
# port = 19000
}
}
module = {
a = { # An MMDVMHost module is shown
# make sure this module letter agrees with the [D-Star] Module letter in MMDVM.ini
# in MMDVM.ini its uppercase, here use lower case.
# We show a here, but the convention on this software is:
# 23 cm modules will use "a"
# 70 cm modules will use "b"
# 2 M module will use "c"
type = "mmdvm"
# ip = "127.0.0.1"
# port = 19998 # default for mod a, you usually don't need to specify this
# frequency = 0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
# offset = 0
# range = 0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
# agl = 0 # the height above ground level for this repeater's antenna
# latitude = 0 # you can leave this unspecified for a mobile rig
# longitude = 0 # like the latitude
# desc1 = "" # maximum of 20 characters, most special symbols are not allowed
# desc2 = "" # just like desc1
# url = "github.com/n7tae/g2_ircddb" # 80 characters max
# In review, for an MMDVM module, you only need to specify the type = "mmdvm" and
# make sure the module letter agrees with what's in "MMDVM.ini.
# Also see the mmdvm section below, although it usually doesn't need specifying.
}
b = {
# a DVRPTR connected to a 70cm radio is shown in this example
# type must be defined. This is how the dvrptr program finds the config params.
type = "dvrptr"
# If you operate in "restriction mode", set callsign equal to your personal callsign
# Otherwise do not set callsign and we will use ircddb.username
# callsign = ""
# the frequency of your DVRPTR in MHz.
# frequency = 145.5
# the TX/RX offset in MHz, use 0.0 for simplex
# offset = 0
# the range of this repeater, in meters 1609.344 meters is 1.0 miles
# range = 0.0
# the height above ground level for you repeater's antenna
# agl = 0.0
# the latitude of your repeater
# latitude = 0.0
# the longitude of your repeater
# longitude = 0.0
# This is used in a loop to wait for packets from your local gateway
# This is in milliseconds
# packet_wait = 25
# description of repeater, part one and two 20 char max each
# desc1 = ""
# desc2 = ""
# the url of your repeater, 80 chars max, defaults to "github.com/n7tae/g2_ircddb"
# url = ""
# If you want to enable/disable the repeater, set these options.
# Each of these options can NOT be more than 8 characters.
# Each of these options can NOT be another user's callsign.
# Each of these options can NOT be another repeater or reflector.
# Each of these options can NOT be a YRCALL command.
# If these options are set, then they can NOT be equal to each other.
# Using the above options, if you use YRCALL=RFISOFF in your radio
# then the repeater will be OFF and no audio will be copied over local RF
# and no audio will be accepted from any remote system.
# these command are disabled by default
# You can choose your own command strings, if you want to enable these
rf_control = {
# on = "RFISON"
# off = "RFISOFF"
}
# To protect the repeater owners from bad STN programs out there
# and to also protect the repeater owners from RF users that abuse the STN stuff
# Reject values in YRCALL that start with STN
# If you want to allow the local RF users to subscribe to remote STN groups,
# then set it to XXX or something that is invalid like 123
# invalid_prefix = "XXX"
# Your DVRPTR V1 serial number
# If you don't know what it is, run the program and look in the log file!
# serial_number = "00.00.00.00"
# internal_ip = "0.0.0.0" # the dvrptr address, usually leave this alone
# port = 19999 # module defaults: A=20000, B=19999, C=19998
# Some settings for your DVRPTR modem (see DVRPTR V1 manual for more info).
# rf_rx_level = 80
# If you need duplex, set it to true
# duplex = false
# Do you want an ACK back ?
# acknowledge = false
# ACK delay in milliseconds(ms)
# Minimum is 1, maximum is 999
# If you do not get a repeater ACK, then make it a higher number
# Some radios get the ACK in 250ms, other radios require 750ms
# ack_delay = 300
# This is the TX-delay in milliseconds and it is used to delay the PTT.
# Seme radios have "SLOW" switching time,
# If your radio switches slow from RX to TX, then give your radio more time to switch, maybe 250 ms
# If your radio switches fast from RX to TX, then you could set it to 100 ms or maybe less
# But the best value should be the one to match your radio, so read the manual for your radio.
# In tests that were done for SLOW and FAST radios, we set it to 100 for FAST radios and 250 for SLOW radios.
# tx_delay = 250
# Dead firmware ?
# Lets say that you set RQST_COUNT=10
# When there is NO local RF activity, (we do NOT receive anything from the DV-RPTR modem),
# then every 2 seconds we request the status from the DV-RPTR modem.
# If the DV-RPTR modem does NOT reply to our command, then after sending the command 10 times(RQST_COUNT)
# we have to assume the firmware in the DV-RPTR modem is DEAD.
# So, we send the command every 2 sedonds, and after sending the command 10 times,
# that is about (2 * 10) = 20 seconds,
# the repeater software will stop if the DV-RPTR modem does not respond after 20 seconds( 2 seconds * 10 times )
# and then the service script will restart the repeater software.
# This is used to protect the repeater owner from BAD firmware.
# Minimum value is 6.
# If you see in the log this: "Modem is not responding... shuttting down"
# Then that means that the firmware died and the DV-RPTR modem stopped responding.
# You can increase the value of RQST_COUNT if you have a slow computer,
# (or maybe your computer is running too many programs and can not service the USB/serial fast enough)
# but we were informed that this is a bug in the firmware and they are trying to fix it.
# So, increasing the value for RQST_COUNT to higher than 10, does not make much sense.
# rqst_count = 10
# These values depend on what type of tranceiver is connected to your DV-RPTR modem
# Use either true or false
inverse = {
# rx = true
# tx = true
}
}
c = {
# a 2m DVAP is shown as an example
# type must be defined. This is how the dvap_rptr program finds the config params.
type = "dvap"
# If you operate in "restriction mode", set RPTR equal to your personal callsign
# Otherwise do not set callsign and it will use ircddb.username
# callsign = ""
# TX DVAP power level -12 to 10, 10 is maximum poower
# power = 10
# Squelch, -128 to -45, -100 to -80 usually works best
# squelch = -100
# To protect the repeater owners from bad STN programs out there
# and to also protect the repeater owners from RF users that abuse the STN stuff
# Reject values in YRCALL that start with STN
# If you want to allow the local RF users to subscribe to remote STN groups,
# then set it to XXX or something that is invalid like 123
# ivalid_prefix = "XXX"
# The serial number of you DVAP is visible through the bottom of the case
# serial_number = "APXXXXXX"
# the DVAP will send a blip to acknowledge a transmission
# acknowledge = false
# the frequency of your DVAP in MHz.
# frequency = 145.5
# this is for tweaking the frequency of your DVAP, see the owner's manual
# dvap_offset = 0
# the range of this repeater, in meters 1609.344 meters is 1.0 miles
# range = 0.0
# the height above ground level for you repeater's antenna
# agl = 0.0
# the latitude of your repeater
# latitude = 0.0
# the longitude of your repeater
# longitude = 0.0
# This is used in a loop to wait for packets from your local gateway
# This is in milliseconds
# packet_wait = 25
# description of repeater, part one and two 20 char max each
# desc1 = ""
# desc2 = ""
# the url of your repeater, 80 chars max
# url = "github.com/n7tae/QnetGateway"
# where other g2 programs find this repeater software
# ip = "127.0.0.1" # where is the device running? must be a "dotted number"
# the internal ip of this program, "0.0.0.0" is usually best
# internal_ip = "0.0.0.0"
# port number default: A:19998 B:19999 C:20000
# port = 20000 # default for mod C
}
}
# you only need this mmdvm section if you set non-standard D-Star ports on your MMDVMHost software
mmdvm = {
# these need to be the same as they are in your MMDVM.ini file (in the [D-Star Network] section
# If you change them there, then change them here!
# gateway_port = 20010
# local_port = 20011
}
log = {
# debuging and extra logging switches
# qso = false # QSO info goes into the log
# irc = false # IRC debug info
# dtmf = false # DTMF debug info
}
aprs = { # APRS.NET connect info
# host = "rotate.aprs.net"
# port = 14580
# interval = 40
# filter = ""
}
link = {
# link_at_start = "NONE" # Link to a reflector at startup.
# to link repeater module B to REF001 C, use "BREF001C"
# ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used
# admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt
# you probabaly want you own callsign in the admin list!
# link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater
# no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist
# if the blacklist is defined (even if it's empty), the link_unlink will not be read
# incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections.
# ip = "127.0.0.1" # where g2_link is running
# port = 18997 # port for communications to g2_link
# ref_port = 20001 # port for REF linking, don't change
# xrf_port = 30001 # port for XRF linking, don't change
# dcs_port = 30051 # port for DCS linking, don't change
# announce = true # do link, unlink, etc. announcements
# acknowledge = true # send text acknowledgement on key-up
# max_dongles = 5 # maximum number of linked hotspots
}
file = {
# status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services
# DTMF = "/tmp" #
# echotest = "/tmp" # echo dat files will end up here
# gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is.
# announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements
}
timing = {
timeout = {
# echo = 1 # delay time in seconds for echo
# voicemail = 1 # delay time for voicemail
# remote_g2 = 2 # after this many seconds with no packets, we assume the tx is closed
# local_rptr = 1 # local timeout, in seconds
}
play = {
# wait = 2 # seconds before playback occurs, between 1 and 10
# delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds
}
inactivity = {
# a = 0 # unlink repeater if no activity for this many minutes
# b = 0 # zero mean there will be no timer
# c = 0
}
}
#
# Copyright (c) 2019 by Thomas A. Early N7TAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#########################################################################################################################
# #
# qn.everything.cfg example configuration file #
# #
#########################################################################################################################
# What follows need to also be valid bash shell variable definitions, therefore:
# No white space on either side of the equal sign (=)
# String values should be quoted if they contain any special chars, including white space
# If a string value is a simple word, it doesn't need to be quoted
# Use the single quote (') for quoting strings, not the double quote(")
# Comments can come after a key=value definition, introduced by a pound-sign (#)
#
# if a definition is commented out, it means that key has that value as its default value.
# You don't need to uncomment it if that value is acceptable. In fact, you can remove the commented
# line altogether to simplify you qn.cfg file! In fact, you can remove all these comments and blank
# lines!
##########################################################################################################################
#
# IRCDDB - You MUST use a legal callsign for logging into any IRC network
#
# you must specify you legal Callsign to enable QnetGateway
ircddb_login='' # login callsign for the ircDDB network
#ircddb_host='rr.openquad.net' # other irc networks include group1-irc.ircddb.net and group2-irc.ircddb.net
#ircddb_port=9007 # not a good idea to change!
#ircddb_password='1111111111111' # not needed for rr.openquad.net
##########################################################################################################################
#
# GATEWAY
#
# Very few users will need to specify anything in the 'gateway' section!
#gateway_bool_regen_header=true # regenerate headers from incoming data
#gateway_send_qrgs_maps=true # send frequency, offset, coordinates and url to irc-server
#gateway_local_irc_ip='0.0.0.0' # the local port on the gateway for the IRC tcp socket
#gateway_external_ip='0.0.0.0' # this means accept a connection from any source
#gateway_external_port=40000 # don't change
#gateway_tolink='gate2link' # Unix sockets between qngateway and QnetLink
#gateway_fromlink='link2gate' # all Unix sockets are on the file system, but hidden from view
##########################################################################################################################
#
# APRS - for tracking users and also this repeater.
#
#aprs_enable=true # uncomment and set this to 'false' if you don't wany the gateway interacting with APRS
#aprs_host='rotate.aprs.net' # the APRS network server
#aprs_port=14580 # and port
#aprs_interval=40 # keep-alive in minutes
#aprs_filter='' # advanced feature
##########################################################################################################################
#
# LINK - controls the behaviour of QnetLink (qnlink)
#
#link_admin='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt
# if empty, everyone has admin privileges.
#link_link_unlink='' # if defined, comma-separated list of users that can link and unlink a repeater
#link_no_link_unlink='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist.
# if a blacklist is defined and not empty, the link_unlink will not be read
#link_incoming_ip='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection.
#link_ref_port=20001 # port for REF linking, don't change
#link_xrf_port=30001 # port for XRF linking, don't change
#link_dcs_port=30051 # port for DCS linking, don't change
#link_announce=true # do link, unlink, etc. announcements
#link_acknowledge=true # send text acknowledgment on key-up
#link_max_dongles=5 # maximum number of linked hot-spots
##########################################################################################################################
#
# Here is an example MMDVM module on 70cm channel B
#
module_b='mmdvm'
#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts.
#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer.
#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty.
#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream
#module_b_acknowledge=false # Do you want an ACK back?
#module_b_ack_delay=250 # millisecond delay before acknowledgement
#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak
#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
#module_b_agl=0 # the height above ground level for this repeater's antenna
#module_b_latitude=0 # you can leave this unspecified for a mobile rig
#module_b_longitude=0 # like the latitude
#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed
#module_b_desc2='' # just like desc1
#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway
#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C
#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max
# MMDVM - Special parameters when: module_b='mmdvm'
#module_b_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program
#module_b_gateway_port=20010 # which port will QnetRelay be sending on
#module_b_local_port=20011 # which port will MMDVMHost be sending on
##########################################################################################################################
#
# Here is an example ICOM Terminal and Access Point (ITAP) module specified on 2m channel C
#
module_c='itap'
#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts.
#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer.
#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty.
#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream
#module_c_acknowledge=false # Do you want an ACK back?
#module_c_ack_delay=250 # millisecond delay before acknowledgement
#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak
#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
#module_c_agl=0 # the height above ground level for this repeater's antenna
#module_c_latitude=0 # you can leave this unspecified for a mobile rig
#module_c_longitude=0 # like the latitude
#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed
#module_c_desc2='' # just like desc1
#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway
#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C
#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max
# ITAP - Special parameters when: module_c='itap'
#module_c_device='/dev/ttyUSB0' # where the serial-to-USB cable shows up
##########################################################################################################################
#
# DVAP - Here is an example 2M dvap
#
module_c='itap'
#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts.
#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer.
#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty.
#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream
#module_c_acknowledge=false # Do you want an ACK back?
#module_c_ack_delay=250 # millisecond delay before acknowledgement
#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak
#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
#module_c_agl=0 # the height above ground level for this repeater's antenna
#module_c_latitude=0 # you can leave this unspecified for a mobile rig
#module_c_longitude=0 # like the latitude
#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed
#module_c_desc2='' # just like desc1
#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway
#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C
#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max
# DVAP - Special parameters when: module_c='dvap'
#module_c_power=10 # TX power level: -12 to 10, 10 is maximum power
#module_c_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best
#module_c_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case
##########################################################################################################################
#
# DVRPTR - Here is an example 70cm dvrptr
#
module_b='dvrptr'
#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts.
#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer.
#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty.
#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream
#module_b_acknowledge=false # Do you want an ACK back?
#module_b_ack_delay=250 # millisecond delay before acknowledgement
#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak
#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles
#module_b_agl=0 # the height above ground level for this repeater's antenna
#module_b_latitude=0 # you can leave this unspecified for a mobile rig
#module_b_longitude=0 # like the latitude
#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed
#module_b_desc2='' # just like desc1
#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway
#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C
#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max
# DVRPTR - Special parameters when: module_b='dvrptr'
# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr
#module_b_serial_number='00.00.00.00' # the DVRPTR serial number
#module_b_rf_on='RFON' # put this in YRCALL to disable the channel
#module_b_rf_off='RFOFF' # put this in YRCALL to enable the channel
#module_b_rf_rx_level=80 # see the DVRPTR V1 manual
#module_b_duplex=false # set to true if the module is duplex
#module_b_tx_delay=250 # milliseconds to allow for switching from rx to tx
#module_b_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed
#module_b_inverse_rx=true # if you're not hearing anything, try false
#module_b_inverse_tx=true # if you're not being heard, try false
##########################################################################################################################
#
# LOGGING - Control extra logging - useful for debugging
#
#log_qso=false # QSO info goes into the log
#log_irc=false # IRC debug info
#log_dtmf=false # DTMF debug info
##########################################################################################################################
#
# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors
#
# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors!
# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail,
# even if QnetLink reports a successful authorization.
#dplus_authorize=false # to enable, uncomment and set to true to link to DPlus reflectors and/or repeaters
#dplus_ref_login='' # for logging into REF reflectors, if empty, ircddb_login will be used
#dplus_use_reflectors=true # set to false if you are not going to link to DPlus reflectors
#dplus_use_repeaters=true # set to false if you are not going to link to DPlus repeaters
# any gateways in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns.
##########################################################################################################################
#
# FILE - where important QnetGateway files and directories are found.
#
#file_status='/usr/local/etc/rptr_status' # where repeater status info is passed between services
#file_dtmf='/tmp' # where DTMF is decoded
#file_echotest='/tmp' # echo dat files will end up here
#file_qnvoicefile='/tmp/qnvoice.txt' # where qnvoice will create the play command
#file_gwys='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is.
#file_announce_dir='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements
##########################################################################################################################
#
# TIMINGS - for controlling how to deal with timing issues
#
# most users will not have to override any of these default values
#timing_timeout_echo=1 # seconds before we assume echo has timed out
#timing_timeout_voicemail=1 # seconds before we assume voicemail has timed out
#timing_timeout_remote_g2=2 # after this many seconds with no packets, we assume the tx is closed
#timing_timeout_local_rptr=1 # local repeater timeout, in seconds
#timing_play_wait=1 # seconds before echo or voicemail playback occurs, between 1 and 10
#timing_play_delay=19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms

@ -0,0 +1,7 @@
# A Simple Configuration File for an ICOM using Terminal and Access Point Mode
ircddb_login='Q1ABC'
module_c='itap'
#dplus_authorize=true # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters

@ -3,41 +3,16 @@
# Please see qn.everything.cfg for many configurable items.
# New-bees beware, it is possible to configure your system
# to a non-functional state. Nearly all configure items
# already have good default vaules, but the few below
# already have good default values, but the two below
# HAVE TO BE SET BY YOU!!!
ircddb = {
ircddb_login='Q1ABC'
# Use your callsign, the default network is QuadNet2
login = "YOUR CALLSIGN"
module_x='mmdvm'
}
module = {
# Change the "x" to the lowercase equivilent of the module
# assignment in you MMDVM.qn initialization file.
# Use B and b for 70cm and C and c for 2M.
x = {
type = "mmdvm"
# See the qn.everything.cfg file if you want to include
# location data for your repeater/hot-spot.
# (Location data in your MMDVM.qn ini file will
# Change the "x" to the lowercase equivilent of the module assignment in you MMDVM.qn initialization file.
# Use B in the .ini file and b in this file for 70cm and C and c for 2M. See the qn.everything.cfg file if you want to include
# location data for your repeater/hot-spot. (Location data in your MMDVM.qn ini file will
# not make it to the D-Star network.)
}
}
link = {
# Only callsigns listed in the admin list can execute
# Linux scripts from you radio. Two are already provided:
# _ _ _ _ _ _ R X reboots your system, and
# _ _ _ _ _ _ H X halts it.
admin = [ "AA0AAA" , "BB1BBB" , "CC3CCC" ]
}
#dplus_authorize='true' # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters

@ -0,0 +1,654 @@
#!/bin/bash
#
# Copyright (c) 2019 by Thomas A. Early N7TAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
trap ControlC INT
ControlC () {
echo "caught SIGINT"
}
InstallMMDVMHosts () {
if [ $nmmdvm -gt 0 ] && [ -e ${MMPATH}/MMDVMHost ]; then
if [ $nmmdvm -eq 1 ]; then
sudo make ${1}installmmdvm
else
sudo make MODULE=${ammdvm[0]} ${1}installmmdvm
sudo make MODULE=${ammdvm[1]} ${1}installmmdvm
if [ $nmmdvm -eq 3 ]; then
sudo make MODULE=${advap[2]} ${1}installmmdvm
fi
fi
fi
}
InstallSystem () {
local n
if [ -z ${1} ]; then
n=$( grep '^processor' /proc/cpuinfo | wc -l )
echo "Detected $n processors for make"
make base -j$n
fi
sudo make ${1}installbase
if [ $ndvap -gt 0 ]; then
if [ -z ${1} ]; then
make qndvap -j$n
fi
if [ $ndvap -eq 1 ]; then
sudo make ${1}installdvap
else
sudo make MODULE=${advap[0]} ${1}installdvap
sudo make MODULE=${advap[1]} ${1}installdvap
if [ $ndvap -eq 3 ]; then
sudo make MODULE=${advap[2]} ${1}installdvap
fi
fi
fi
if [ $ndvrptr -gt 0 ]; then
if [ -z ${1} ]; then
make qndvrptr -j$n
fi
if [ $ndvrptr -eq 1 ]; then
sudo make ${1}installdvrptr
else
sudo make MODULE=${advrptr[0]} ${1}installdvrptr
sudo make MODULE=${advrptr[1]} ${1}installdvrptr
if [ $ndvrptr -eq 3 ]; then
sudo make MODULE=${advrptr[2]} ${1}installdvrptr
fi
fi
fi
if [ $nitap -gt 0 ]; then
if [ -z ${1} ]; then
make qnitap -j$n
fi
if [ $nitap -eq 1 ]; then
sudo make ${1}installitap
else
sudo make MODULE=${aitap[0]} ${1}installitap
sudo make MODULE=${aitap[1]} ${1}installitap
if [ $nitap -eq 3 ]; then
sudo make MODULE=${aitap[2]} ${1}installitap
fi
fi
fi
if [ $nmmdvm -gt 0 ]; then
if [ -z ${1} ]; then
make qnrelay -j$n
fi
if [ $nmmdvm -eq 1 ]; then
sudo make ${1}installrelay
else
sudo make MODULE=${ammdvm[0]} ${1}installrelay
sudo make MODULE=${ammdvm[1]} ${1}installrelay
if [ $nmmdvm -eq 3 ]; then
sudo make MODULE=${advap[2]} ${1}installrelay
fi
fi
fi
if [ $nmodem -gt 0 ]; then
if [ -z ${1} ]; then
make qnmodem -j$n
fi
if [ $nmodem -eq 1 ]; then
sudo make ${1}installmodem
else
sudo make MODULE=${amodem[0]} ${1}installmodem
sudo make MODULE=${amodem[1]} ${1}installmodem
if [ $nmmdvm -eq 3 ]; then
sudo make MODULE=${amodem[2]} ${1}installmodem
fi
fi
fi
}
BaseStatus () {
local LoadGate SubGate LoadLink SubLink LoadDTMF SubDTMF
LoadGate=$( systemctl show -p LoadState --value qngateway )
SubGate=$( systemctl show -p SubState --value qngateway )
LoadLink=$( systemctl show -p LoadState --value qnlink )
SubLink=$( systemctl show -p SubState --value qnlink )
LoadDTMF=$( systemctl show -p LoadState --value qndtmf )
SubDTMF=$( systemctl show -p SubState --value qndtmf )
if [[ $LoadGate == 'loaded' ]]; then
if [[ $SubGate == 'running' ]]; then
GateState='running'
else
GateState='stopped'
fi
else
GateState='not installed'
fi
echo "QnetGateway is $GateState"
if [[ $LoadLink == 'loaded' ]]; then
if [[ $SubLink == 'running' ]]; then
LinkState='running'
else
LinkState='stopped'
fi
else
LinkState='not installed'
fi
echo "QnetLink is $LinkState"
if [[ $LoadDTMF == 'loaded' ]]; then
if [[ $SubDTMF == 'running' ]]; then
DTMFState='running'
else
DTMFState='stopped'
fi
else
DTMFState='not installed'
fi
echo "DTMF is $DTMFState"
}
ModuleStatus () {
local LoadState SubState mcvar
if [ -z ${3} ]; then
ModuleState[$1]='EMPTY'
MMDVMState[$1]='EMPTY'
else
mcvar="n${3}"
ModuleProcess[$1]="qn$3"
if [[ "${ModuleProcess[$1]}" == 'qnmmdvmhost' ]]; then
ModuleProcess[$1]='qnrelay'
MMDVMProcess[$1]='mmdvm'
MMDVMState[$1]='not installed'
elif [[ "${ModuleProcess[$1]}" == 'qnmmdvmmodem' ]]; then
ModuleProcess[$1]='qnmodem'
MMDVMProcess[$1]=''
MMDVMState[$1]='EMPTY'
else
MMDVMState[$1]='EMPTY'
MMDVMProcess[$1]=''
fi
if [[ ${!mcvar} > 1 ]]; then
ModuleProcess[$1]="${ModuleProcess[$1]}${2}"
if [[ "${MMDVMProcess[$1]}" == 'mmdvm' ]]; then
MMDVMProcess[$1]="${MMDVMProcess[$1]}${2}"
fi
fi
LoadState=$( systemctl show -p LoadState --value ${ModuleProcess[$1]} )
SubState=$( systemctl show -p SubState --value ${ModuleProcess[$1]} )
if [[ "$LoadState" == "loaded" ]]; then
if [[ $SubState == "running" ]]; then
ModuleState[$1]='running'
else
ModuleState[$1]='stopped'
fi
else
ModuleState[$1]='not installed'
fi
if [[ "${MMDVMState[$1]}" != 'EMPTY' ]]; then
LoadState=$( systemctl show -p LoadState --value ${MMDVMProcess[$1]} )
SubState=$( systemctl show -p SubState --value ${MMDVMProcess[$1]} )
if [[ "$LoadState" == "loaded" ]]; then
if [[ $SubState == "running" ]]; then
MMDVMState[$1]='running'
else
MMDVMState[$1]='stopped'
fi
else
MMDVMState[$1]='not installed'
fi
fi
if [[ "$3" == 'mmdvmhost' ]]; then
echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]} - ${MMDVMProcess[$1]} is ${MMDVMState[$1]}"
else
echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]}"
fi
fi
}
Header () {
local count
count=$( ps -aux | grep -e qn -e MMDVMHost | wc -l )
if [ ${count} -gt 3 ]; then
echo
echo "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND"
ps -aux | grep -e qngateway -e qnlink -e qndtmf -e qndvap -e qnitap -e qnrelay -e qndvrptr -e qnmodem -e MMDVMHost | grep -v grep
fi
echo
}
LogMenu () {
ans=''
while [[ "$ans" != q* ]]; do
clear
echo " Log Menu"
Header
echo "After starting a log, use <Ctrl>+C to stop the log and return to this menu."
echo
if [[ $GateState == 'running' ]]; then
echo " g : qngateway Log"
else
echo " qngateway is not running"
fi
if [[ $LinkState == 'running' ]]; then
echo " l : qnlink Log"
else
echo " qnlink is not running"
fi
if [[ $DTMFState == 'running' ]]; then
echo " d : qndtmf Log"
else
echo " qndtmf is not running"
fi
if [[ ${ModuleState[0]} != 'EMPTY' ]]; then
if [[ ${ModuleState[0]} == 'running' ]]; then
echo " a : ${ModuleProcess[0]} Log"
else
echo " ${ModuleProcess[0]} is not running"
fi
fi
if [[ ${ModuleState[1]} != 'EMPTY' ]]; then
if [[ ${ModuleState[1]} == 'running' ]]; then
echo " b : ${ModuleProcess[1]} Log"
else
echo " ${ModuleProcess[1]} is not running"
fi
fi
if [[ ${ModuleState[2]} != 'EMPTY' ]]; then
if [[ ${ModuleState[2]} == 'running' ]]; then
echo " c : ${ModuleProcess[2]} Log"
else
echo " ${ModuleProcess[2]} is not running"
fi
fi
if [[ ${MMDVMState[0]} != 'EMPTY' ]]; then
if [[ ${MMDVMState[0]} == 'running' ]]; then
echo " x : ${MMDVMProcess[0]} Log"
else
echo " ${MMDVMProcess[0]} is not running"
fi
fi
if [[ ${MMDVMState[1]} != 'EMPTY' ]]; then
if [[ ${MMDVMState[1]} == 'running' ]]; then
echo " y : ${MMDVMProcess[1]} Log"
else
echo " ${MMDVMProcess[1]} is not running"
fi
fi
if [[ ${MMDVMState[2]} != 'EMPTY' ]]; then
if [[ ${MMDVMState[2]} == 'running' ]]; then
echo " z : ${MDVMProcess[2]} Log"
else
echo " ${MMDVMProcess[2]} is not running"
fi
fi
echo
echo " q : quit and return to main menu"
echo
read -p "Command: " ans
if [[ "$ans" == g* ]] && [[ "$GateState" == 'running' ]]; then
sudo journalctl -u qngateway -f
elif [[ "$ans" == l* ]] && [[ "$LinkState" == 'running' ]]; then
sudo journalctl -u qnlink -f
elif [[ "$ans" == d* ]] && [[ "$DTMFState" == 'running' ]]; then
sudo journalctl -u qndtmf -f
elif [[ "$ans" == a* ]] && [[ "${ModuleState[0]}" == 'running' ]]; then
sudo journalctl -u ${ModuleProcess[0]} -f
elif [[ "$ans" == b* ]] && [[ "${ModuleState[1]}" == 'running' ]]; then
sudo journalctl -u ${ModuleProcess[1]} -f
elif [[ "$ans" == c* ]] && [[ "${ModuleState[2]}" == 'running' ]]; then
sudo journalctl -u ${ModuleProcess[2]} -f
elif [[ "$ans" == x* ]] && [[ "${MMDVMState[0]}" == 'running' ]]; then
sudo journalctl -u ${MMDVMProcess[0]} -f
elif [[ "$ans" == y* ]] && [[ "${MMDVMState[1]}" == 'running' ]]; then
sudo journalctl -u ${MMDVMProcess[1]} -f
elif [[ "$ans" == z* ]] && [[ "${MMDVMState[2]}" == 'running' ]]; then
sudo journalctl -u ${MMDVMProcess[2]} -f
fi
done
ans=''
}
MaintenanceMenu () {
local m n
ans=''
while [[ "$ans" != q* ]]; do
clear
echo " MaintenanceMenu"
Header
if [[ "$GateState" == 'running' ]]; then
echo " g : Stop QnetGateway"
elif [[ "$GateState" == 'stopped' ]]; then
echo " g : Start QnetGateway"
fi
if [[ "$LinkState" == 'running' ]]; then
echo " l : Stop QnetLink"
elif [[ "$LinkState" == 'stopped' ]]; then
echo " l : Start QnetLink"
fi
if [[ "$DTMFState" == 'running' ]]; then
echo " d : Stop DTMF"
elif [[ "$DTMFState" == 'stopped' ]]; then
echo " d : Start DTMF"
fi
if [[ "${ModuleState[0]}" == 'running' ]]; then
echo " a : Stop Module A"
elif [[ "${ModuleState[0]}" == 'stopped' ]]; then
echo " a : Start Module A"
fi
if [[ "${ModuleState[1]}" == 'running' ]]; then
echo " b : Stop Module B"
elif [[ "${ModuleState[1]}" == 'stopped' ]]; then
echo " b : Start Module B"
fi
if [[ "${ModuleState[2]}" == 'running' ]]; then
echo " c : Stop Module C"
elif [[ "${ModuleState[2]}" == 'stopped' ]]; then
echo " c : Start Module C"
fi
if [[ "${MMDVMState[0]}" == 'running' ]]; then
echo " x : Stop MMDVMHost A"
elif [[ "${MMDVMState[0]}" == 'stopped' ]]; then
echo " x : Start MMDVMHost A"
fi
if [[ "${MMDVMState[1]}" == 'running' ]]; then
echo " y : Stop MMDVMHost B"
elif [[ "${MMDVMState[1]}" == 'stopped' ]]; then
echo " y : Start MMDVMHost B"
fi
if [[ "${MMDVMState[2]}" == 'running' ]]; then
echo " z : Stop MMDVMHost C"
elif [[ "${MMDVMState[2]}" == 'stopped' ]]; then
echo " z : Start MMDVMHost C"
fi
echo
echo " q : Return to main Menu"
echo
read -p "Command: " ans
if [[ "$ans" == g* ]]; then
if [[ "$GateState" == 'running' ]]; then
sudo systemctl stop qngateway
elif [[ "$GateState" == 'stopped' ]]; then
sudo systemctl start qngateway
fi
BaseStatus
elif [[ "$ans" == l* ]]; then
if [[ "$LinkState" == 'running' ]]; then
sudo systemctl stop qnlink
elif [[ "$LinkState" == 'stopped' ]]; then
sudo systemctl start qnlink
fi
BaseStatus
elif [[ "$ans" == d* ]]; then
if [[ "$DTMFState" == 'running' ]]; then
sudo systemctl stop qndtmf
elif [[ "$DTMFState" == 'stopped' ]]; then
sudo systemctl start qndtmf
fi
BaseStatus
elif [[ "$ans" == a* ]] && [ -n $module_a ]; then
if [[ "${ModuleState[0]}" == 'running' ]]; then
sudo systemctl stop ${ModuleProcess[0]}
elif [[ "${ModuleState[0]}" == 'stopped' ]]; then
sudo systemctl start ${ModuleProcess[0]}
fi
ModuleStatus 0 a $module_a
elif [[ "$ans" == b* ]] && [ -n $module_b ]; then
if [[ "${ModuleState[1]}" == 'running' ]]; then
sudo systemctl stop ${ModuleProcess[1]}
elif [[ "${ModuleState[1]}" == 'stopped' ]]; then
sudo systemctl start ${ModuleProcess[1]}
fi
ModuleStatus 1 b $module_b
elif [[ "$ans" == c* ]] && [ -n $module_c ]; then
if [[ "${ModuleState[2]}" == 'running' ]]; then
sudo systemctl stop ${ModuleProcess[2]}
elif [[ "${ModuleState[2]}" == 'stopped' ]]; then
sudo systemctl start ${ModuleProcess[2]}
fi
ModuleStatus 2 c $module_c
elif [[ "$ans" == x* ]] && [ -n $module_a ]; then
if [[ "${MMDVMState[0]}" == 'running' ]]; then
sudo systemctl stop ${MMDVMProcess[0]}
elif [[ "${MMDVMState[0]}" == 'stopped' ]]; then
sudo systemctl start ${MMDVMProcess[0]}
fi
ModuleStatus 0 a $module_a
elif [[ "$ans" == y* ]] && [ -n $module_b ]; then
if [[ "${MMDVMState[1]}" == 'running' ]]; then
sudo systemctl stop ${MMDVMProcess[1]}
elif [[ "${MMDVMState[1]}" == 'stopped' ]]; then
sudo systemctl start ${MMDVMProcess[1]}
fi
ModuleStatus 1 b $module_b
elif [[ "$ans" == z* ]] && [ -n $module_c ]; then
if [[ "${MMDVMState[2]}" == 'running' ]]; then
sudo systemctl stop ${MMDVMProcess[2]}
elif [[ "${MMDVMState[2]}" == 'stopped' ]]; then
sudo systemctl start ${MMDVMProcess[2]}
fi
ModuleStatus 2 c $module_c
fi
done
ans=''
}
GatewayMenu () {
ans=''
while [[ "$ans" != q* ]]; do
local refcount=$( grep -s "^REF" gwys.txt | wc -l )
local dcscount=$( grep -s "^DCS" gwys.txt | wc -l )
local xrfcount=$( grep -s "^XRF" gwys.txt | wc -l )
local repcount=$( grep -s -v "^#" gwys.txt | grep -s -v -e "^REF" -e "^DCS" -e "^XRF" | wc -l )
clear
echo
echo " Gateway File"
echo
echo " $refcount REF, $dcscount DCS, $xrfcount XRF and $repcount Repeaters"
echo
echo " All initialization will overwrite any existing file!"
echo "ip : Initialize gwys.txt using www.PiStar.uk/downloads and xlxapi.rlx.lu"
echo "iv : Initialize gwys.txt using VA3UV.com (includes a large # of repeaters)"
echo "ir : Initialize gwys.txt using VA3UV.com (but just the reflectors)"
echo "n : Edit gwys.txt file with nano"
echo "v : Edit gwys.txt file with vi"
echo
echo "q : Quit and return to Main Menu"
echo
read -p "Command: " ans
if [[ "$ans" == ip* ]]; then
wget -O XLX_Hosts.txt http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster
wget http://www.pistar.uk/downloads/DExtra_Hosts.txt
wget http://www.pistar.uk/downloads/DPlus_Hosts.txt
wget http://www.pistar.uk/downloads/DCS_Hosts.txt
/bin/rm -f gwys.txt
echo "# Downloaded from www.pistar.uk and xlxapi.rlx.lu `date`" > gwys.txt
awk '$1 ~ /^XLX/ { gsub("\r", ""); printf "%s %s 30001\n", $1, $2 }' XLX_Hosts.txt >> gwys.txt
awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt
awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt
awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt
/bin/rm -f {XLX,DExtra,DPlus,DCS}_Hosts.txt
elif [[ "$ans" == iv* ]]; then
/bin/rm -f gwys.txt
wget http://www.va3uv.com/gwys.txt
elif [[ "$ans" == ir* ]]; then
/bin/rm -f gwys.txt
wget -nv -O gwys.va3uv.txt http://www.va3uv.com/gwys.txt
if [ -e gwys.va3uv.txt ]; then
echo "# Downloaded from www.va3uv.com `date`" > gwys.txt
awk '$1~/^REF/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt
awk '$1~/^XRF/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt
awk '$1~/^DCS/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt
rm -f gwys.va3uv.txt
fi
elif [[ "$ans" == n* ]]; then
nano gwys.txt
elif [[ "$ans" == v* ]]; then
vi gwys.txt
fi
done
ans=''
}
# get defined modules from the config file
if [ -e qn.cfg ]; then
source <( grep "^module_[abc]=" qn.cfg )
if [ -z "$module_a" ] && [ -z "$module_b" ] && [ -z "$module_c" ]; then
echo "No moudules defined in the qn.cfg file!, please re-edit you configuration."
exit 1
fi
else
echo "ERROR: can't find the qn.cfg configuration file!"
echo "You can use ./qnconfig to create your config file."
exit 1
fi
# get the installation directory from the make file
if [ -e makefile ]; then
MAKEFILE=makefile
elif [ -e Makefile ]; then
MAKEFILE=Makefile
else
echo "ERROR: There is no Makefile or makefile"
exit 1
fi
source <( grep -e '^BINDIR=' -e '^MMPATH=' $MAKEFILE )
if [ -z $BINDIR ]; then
echo "ERROR: The BINDIR definition in $MAKEFILE is empty!"
exit 1
fi
if [ ! -d "$BINDIR" ]; then
echo "ERROR: The BINDIR directory $BINDIR is not a directory!"
exit 1
fi
ndvap=0
ndvrptr=0
nitap=0
nmmdvm=0
nmodem=0
for m in a b c ; do
mod=module_${m}
if [ -z ${!mod} ]; then continue; fi
type=${!mod}
if [[ "$type" == 'dvap' ]]; then
advap[${ndvap}]=${m}
ndvap=$((ndvap + 1))
elif [[ "$type" == 'dvrptr' ]]; then
advrptr[$ndvap]=${m}
ndvrptr=$((ndvrptr + 1))
elif [[ "$type" == 'itap' ]]; then
aitap[${nitap}]=${m}
nitap=$((nitap + 1))
elif [[ "$type" == 'mmdvmhost' ]]; then
ammdvm[${nmmdvm}]=${m}
nmmdvm=$((nmmdvm + 1))
elif [[ "$type" == 'mmdvmmodem' ]]; then
amodem[${nmodem}]=${m}
nmodem=$((nmodem + 1))
fi
done
MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm + nmodem))
while [[ "$ans" != q* ]]; do
clear
echo
echo " Qnet Administration Menu"
Header
BaseStatus
ModuleStatus 0 a "$module_a"
ModuleStatus 1 b "$module_b"
ModuleStatus 2 c "$module_c"
echo
if [[ "$GateState" == 'not installed' ]] || [[ "$LinkState" == 'not installed' ]]; then
if [ -e gwys.txt ]; then
echo "is : Install configured system"
echo "gp : Do a 'git pull' to refresh sources"
else
echo " You must set-up your gwys.txt file before installation."
fi
else
echo "us : Uninstall configured System"
fi
if [ -e "${MMPATH}/MMDVMHost" ] && [ $nmmdvm -gt 0 ]; then
if [[ "${MMDVMState[0]}" != 'EMPTY' ]] || [[ "${MMDVMState[1]}" != 'EMPTY' ]] || [[ "${MMDVMState[2]}" != 'EMPTY' ]]; then
if [[ "${MMDVMState[0]}" == 'not installed' ]] || [[ "${MMDVMState[1]}" == 'not installed' ]] || [[ "${MMDVMState[2]}" == 'not installed' ]]; then
echo "im : Install MMDVMHost(s)"
else
echo "um : Uninstall MMDVMHost(s)"
fi
fi
fi
echo "gw : Gateway File Menu"
echo
if [[ "$DTMFState" == 'not installed' ]]; then
echo "nd : Edit DTMF file with nano"
echo "vd : Edit DTMF file with vi"
if [ -e qndtmf ] && [ -n "`diff -q qndtmf qndtmf.sh`" ]; then
echo "rd : Restore DTMF file to default (this will overwrite existing DTMF file)"
fi
echo "id : Install DTMF"
else
echo "ud : Uninstall DTMF"
fi
echo
echo "c : Clean (remove temporary files and locally built executables)"
echo
if [[ "$GateState" != 'not installed' ]] && [[ "$LinkState" != 'not installed' ]]; then
echo "m : Maintenance Menu"
echo "l : Log Menu"
fi
echo
read -p "q to quit. Command: " ans
# EXECUTE COMMANDS
if [[ "$GateState" == 'not installed' ]] && [[ "$ans" == is* ]]; then
InstallSystem
echo 'Wait for a few seconds for everything to start...'
sleep 5
elif [[ "$GateState" != 'not installed' ]] && [[ "$ans" == us* ]]; then
InstallSystem un
elif { [[ "${MMDVMState[0]}" == 'not installed' ]] || [[ "${MMDVMState[1]}" == 'not installed' ]] || [[ "${MMDVMState[2]}" == 'not installed' ]]; } && [ -e ${MMPATH}/MMDVMHost ] && [[ "$ans" == im* ]]; then
InstallMMDVMHosts
elif { [[ "${MMDVMState[0]}" == 'stopped' ]] || [[ "${MMDVMState[0]}" == 'running' ]] || [[ "${MMDVMState[1]}" == 'stopped' ]] || [[ "${MMDVMState[1]}" == 'running' ]] || [[ "${MMDVMState[2]}" == 'stopped' ]] || [[ "${MMDVMState[2]}" == 'running' ]]; } && [ -e ${MMPATH}/MMDVMHost ] && [[ "$ans" == um* ]]; then
InstallMMDVMHosts un
elif [[ "$GateState" == 'not installed' ]] && [[ "$ans" == gp* ]]; then
git pull
read -p 'Press <Enter> to continue: ' ans
ans=''
elif [[ "$ans" == gw* ]]; then
GatewayMenu
elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == nd* ]]; then
if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi
nano qndtmf
elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == vd* ]]; then
if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi
vi qndtmf
elif [[ "$DTMFState" == 'not installed' ]] && [ -e qndtmf ] && [ -n "`diff -q qndtmf qndtmf.sh`" ] && [[ "$ans" == rd* ]]; then
rm -f qndtmf
elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == id* ]]; then
if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi
sudo make installdtmf
elif [[ "$DTMFState" != 'not installed' ]] && [[ "$ans" == ud* ]]; then
sudo make uninstalldtmf
elif [[ "$ans" == c* ]]; then
make clean
elif [[ "$ans" == m* ]]; then
MaintenanceMenu
elif [[ "$ans" == l* ]]; then
LogMenu
fi
done
exit 0

@ -0,0 +1,717 @@
#!/bin/bash
#
# Copyright (c) 2019-2020 by Thomas A. Early N7TAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SetBooleanValue () {
local dvname
local cv
if [ -z $2 ]; then
if [ -z ${!1+x} ]; then
if [[ "$1" == module_[abc]_* ]]; then
echo matches
dvname=${1//_[abc]_/_x_}
else
echo does not match
dvname=${1}_d
fi
cv=${!dvname}
else
cv=${!1}
fi
if [[ $cv == [tT]* ]]; then
eval ${1}=false
else
eval ${1}=true
fi
elif [[ "$2" == [tT]* ]]; then
eval ${1}=true
else
eval ${1}=false
fi
}
EvaluateVar () {
if [ -z ${!1+x} ]; then
if [ -z "${!2}" ]; then
echo "'' <DEFAULT>"
else
echo "${!2} <DEFAULT>"
fi
else
if [ -z "${!1}" ]; then
echo "''"
else
echo "${!1}"
fi
fi
}
EndMenu () {
echo
echo "u <key> to unset the value of key (revert to the default value)."
echo "q to return to the main menu"
read -p "Please input: <key> <new_value> # omit value to toggle a true/false : " key value
}
LinkMenu () {
key=''
while [[ "$key" != q* ]]; do
clear
echo
echo " Link/D-Plus Menu"
echo
echo " 'CSV' means Comma Separated Values (of callsigns)"
echo " An empty CVS means everybody has permission"
echo -n "ad : CSV of calls that can execute scripts = "; EvaluateVar link_admin{,_d}
echo -n "li : CSV of calls that can link and unlink = "; EvaluateVar link_link_unlink{,_d}
echo -n "n : CSV of calls that cannot link&unlink = "; EvaluateVar link_no_link_unlink{,_d}
if [ -n "$em" ]; then
echo -n "i : Incoming IP address of QnetLink = "; EvaluateVar link_incoming_ip{,_d}
echo -n "r : UDP port for REF linking = "; EvaluateVar link_ref_port{,_d}
echo -n "x : UDP port for XRF linking = "; EvaluateVar link_xrf_port{,_d}
echo -n "d : DCS port for XRF linking = "; EvaluateVar link_dcs_port{,_d}
echo -n "an : Announce linking = "; EvaluateVar link_announce{,_d}
echo -n "ac : Acknowledge link on each keyup = "; EvaluateVar link_acknowledge{,_d}
echo -n "m : Maximum # of dongles allowed = "; EvaluateVar link_max_dongles{,_d}
fi
echo
echo " Legacy D-Plus Repeaters and Reflectors"
echo -n "au : Authorize Legacy D-Plus Linking = "; EvaluateVar dplus_authorize{,_d}
echo -n "ap : DPlus Priority = "; EvaluateVar dplus_priority{,_d}
if [ -n "$em" ]; then
echo -n "lo : Login call for authorization server = "; EvaluateVar dplus_ref_login{,_d}
echo -n "RF : Add legacy reflectors to gateway list = "; EvaluateVar dplus_use_reflectors{,_d}
echo -n "RP : add legacy repeaters to gateway list = "; EvaluateVar dplus_use_repeaters{,_d}
fi
EndMenu
if [[ "$key" == ad* ]]; then link_admin="${value^^}"
elif [[ "$key" == li* ]]; then link_link_unlink="${value^^}"
elif [[ "$key" == n* ]]; then link_no_link_unlink="${value^^}"
elif [[ "$key" == i* ]]; then link_incoming_ip="$value"
elif [[ "$key" == r* ]]; then link_ref_port="$value"
elif [[ "$key" == x* ]]; then link_xrf_port="$value"
elif [[ "$key" == d* ]]; then link_dcs_port="$value"
elif [[ "$key" == an* ]]; then SetBooleanValue link_announce "$value"
elif [[ "$key" == ac* ]]; then SetBooleanValue link_acknowledge "$value"
elif [[ "$key" == m* ]]; then link_max_dongles="$value"
elif [[ "$key" == au* ]]; then SetBooleanValue dplus_authorize "$value"
elif [[ "$key" == ap* ]]; then SetBooleanValue dplus_priority "$value"
elif [[ "$key" == lo* ]]; then dplus_ref_login="${value^^}"
elif [[ "$key" == RF* ]]; then SetBooleanValue dplus_use_reflectors "$value"
elif [[ "$key" == RP* ]]; then SetBooleanValue dplus_use_repeaters "$value"
elif [[ "$key" == u* ]]; then
if [[ "$value" == ad* ]]; then unset link_admin
elif [[ "$value" == li* ]]; then unset link_link_unlink
elif [[ "$value" == n* ]]; then unset link_no_link_unlink
elif [[ "$value" == i* ]]; then unset link_incoming_ip
elif [[ "$value" == r* ]]; then unset link_ref_port
elif [[ "$value" == x* ]]; then unset link_xrf_port
elif [[ "$value" == d* ]]; then unset link_dcs_port
elif [[ "$value" == an* ]]; then unset link_announce
elif [[ "$value" == ac* ]]; then unset link_acknowledge
elif [[ "$value" == m* ]]; then unset link_max_dongles
elif [[ "$value" == au* ]]; then unset dplus_authorize
elif [[ "$value" == ap* ]]; then unset dplus_priority
elif [[ "$value" == lo* ]]; then unset dplus_ref_login
elif [[ "$value" == RF* ]]; then unset dplus_use_reflectors
elif [[ "$value" == RP* ]]; then unset dplus_use_repeaters
fi
fi
done
}
FileMenu () {
key=''
while [[ "$key" != q* ]]; do
clear
echo
if [ -z "$em" ]; then
echo " Debugging Menu"
else
echo " Debugging/Files/Timings Menu"
fi
echo
echo " Additional entries in log files (in /usr/local/var)"
echo -n "cl : Call(QSO) logging = "; EvaluateVar log_qso{,_d}
echo -n "il : IRC logging = "; EvaluateVar log_irc{,_d}
echo -n "dl : DTMF logging = "; EvaluateVar log_dtmf{,_d}
echo -n "xl : Debug logging = "; EvaluateVar log_debug{,_d}
echo " WARNING debug logging can produce large log file!"
if [ -n "$em" ]; then
echo
echo " Files and directories"
echo -n "sf : Repeater status file = "; EvaluateVar file_status{,_d}
echo -n "ed : Echo/Voicemail directory = "; EvaluateVar file_echotest{,_d}
echo -n "dd : DTMF directory = "; EvaluateVar file_dtmf{,_d}
echo -n "vf : QnetVoice filename = "; EvaluateVar file_qnvoicefile{,_d}
echo -n "gf : Gateways filename = "; EvaluateVar file_gwys{,_d}
echo -n "ad : Announce directory = "; EvaluateVar file_announce_dir{,_d}
echo
echo " Timing controls"
echo -n "et : Echo timeout (sec) = "; EvaluateVar timing_timeout_echo{,_d}
echo -n "vt : Voicemail timeout (sec) = "; EvaluateVar timing_timeout_voicemail{,_d}
echo -n "gt : G2 timeout (sec) = "; EvaluateVar timing_timeout_remote_g2{,_d}
echo -n "rt : Repeater timeout (sec) = "; EvaluateVar timing_timeout_local_rptr{,_d}
echo -n "pw : Echo play wait (sec) = "; EvaluateVar timing_play_wait{,_d}
echo -n "pd : Echo play delay (msec) = "; EvaluateVar timing_play_delay{,_d}
fi
EndMenu
if [[ "$key" == sf* ]]; then file_status="$value"
elif [[ "$key" == ed* ]]; then file_echotest="$value"
elif [[ "$key" == dd* ]]; then file_dtmf="$value"
elif [[ "$key" == vf* ]]; then file_qnvoicefile="$value"
elif [[ "$key" == gf* ]]; then file_gwys="$value"
elif [[ "$key" == ad* ]]; then file_announce_dir="$value"
elif [[ "$key" == cl* ]]; then SetBooleanValue log_qso "$value"
elif [[ "$key" == il* ]]; then SetBooleanValue log_irc "$value"
elif [[ "$key" == dl* ]]; then SetBooleanValue log_dtmf "$value"
elif [[ "$key" == xl* ]]; then SetBooleanValue log_debug "$value"
elif [[ "$key" == et* ]]; then timing_timeout_echo="$value"
elif [[ "$key" == vt* ]]; then timing_timeout_voicemail="$value"
elif [[ "$key" == gt* ]]; then timing_timeout_remote_g2="$value"
elif [[ "$key" == rt* ]]; then timing_timeout_local_rptr="$value"
elif [[ "$key" == pw* ]]; then timing_play_wait="$value"
elif [[ "$key" == pd* ]]; then timing_play_delay="$value"
elif [[ "$key" == u* ]]; then
if [[ "$value" == sf* ]]; then unset file_status
elif [[ "$value" == ed* ]]; then unset file_echotest
elif [[ "$value" == dd* ]]; then unset file_dtmf
elif [[ "$value" == vf* ]]; then unset file_qnvoicefile
elif [[ "$value" == gf* ]]; then unset file_gwys
elif [[ "$value" == ad* ]]; then unset file_announce_dir
elif [[ "$value" == cl* ]]; then unset log_qso
elif [[ "$value" == il* ]]; then unset log_irc
elif [[ "$value" == dl* ]]; then unset log_dtmf
elif [[ "$value" == xl* ]]; then unset log_debug
elif [[ "$value" == et* ]]; then unset timing_timeout_echo
elif [[ "$value" == vt* ]]; then unset timing_timeout_voicemail
elif [[ "$value" == gt* ]]; then unset timing_timeout_remote_g2
elif [[ "$value" == rt* ]]; then unset timing_timeout_local_rptr
elif [[ "$value" == pw* ]]; then unset timing_play_wait
elif [[ "$value" == pd* ]]; then unset timing_play_delay
fi
fi
done
}
IrcddbMenu () {
key=''
while [[ "$key" != q* ]]; do
clear
echo
echo " IRCDDB Menu"
echo
echo "Routes to other clients using IRC Server A will be searched first"
echo
echo "l : Login - must be a legal callsign = '${ircddb_login}'"
echo -n "ha : Hostname for IRC Server A = "; EvaluateVar ircddb0_host{,_d}
if [ -n "$em" ]; then
echo -n "oa : IRC TCP port A = "; EvaluateVar ircddb0_port{,_d}
echo -n "aa : IRC Password A = "; EvaluateVar ircddb0_password{,_d}
fi
echo -n "hb : Hostname for IRC Server B = "; EvaluateVar ircddb1_host{,_d}
if [ -n "$em" ]; then
echo -n "ob : IRC TCP port B = "; EvaluateVar ircddb1_port{,_d}
echo -n "ab : IRC Password B = "; EvaluateVar ircddb1_password{,_d}
fi
EndMenu
if [[ "$key" == l* ]]; then ircddb_login="${value^^}"
elif [[ "$key" == ha* ]]; then ircddb0_host="$value"
elif [[ "$key" == oa* ]]; then ircddb0_port="$value"
elif [[ "$key" == aa* ]]; then ircddb0_password="$value"
elif [[ "$key" == hb* ]]; then ircddb1_host="$value"
elif [[ "$key" == ob* ]]; then ircddb1_port="$value"
elif [[ "$key" == ab* ]]; then ircddb1_password="$value"
elif [[ "$key" == u* ]]; then
if [[ "$value" == l* ]]; then unset ircddb_login
elif [[ "$value" == ha* ]]; then unset ircddb0_host
elif [[ "$value" == oa* ]]; then unset ircddb0_port
elif [[ "$value" == aa* ]]; then unset ircddb0_password
elif [[ "$value" == hb* ]]; then unset ircddb1_host
elif [[ "$value" == ob* ]]; then unset ircddb1_port
elif [[ "$value" == ab* ]]; then unset ircddb1_password
fi
fi
done
}
GateMenu () {
key=''
while [[ "$key" != q* ]]; do
clear
echo
echo " Gateway/APRS Menu"
echo
echo " Gateway Option"
if [ -n "$em" ]; then
echo -n "r : Regenerate Headers = "; EvaluateVar gateway_header_regen{,_d}
echo -n "s : Send IRC network Module Info = "; EvaluateVar gateway_send_qrgs_maps{,_d}
echo -n "i : IRC TCP local network address = "; EvaluateVar gateway_local_irc_ip{,_d}
echo -n "a : G2 port address = "; EvaluateVar gateway_ip{,_d}
echo -n "p : G2 port number = "; EvaluateVar gateway_port{,_d}
echo -n "tl : UNIX socket to QnetLink = "; EvaluateVar gateway_gate2link{,_d}
echo -n "fl : UNIX socket from QnetLink = "; EvaluateVar gateway_link2gate{,_d}
echo -n "ta : UNIX socket to Modem A = "; EvaluateVar gateway_gate2modema{,_d}
echo -n "tb : UNIX socket to Modem B = "; EvaluateVar gateway_gate2modemb{,_d}
echo -n "tc : UNIX socket to Modem C = "; EvaluateVar gateway_gate2modemc{,_d}
echo -n "fm : UNIX socket from Modem(s) = "; EvaluateVar gateway_modem2gate{,_d}
fi
echo -n "la : Latitude (-90.0 to 90.0) = "; EvaluateVar gateway_latitude{,_d}
echo -n "lo : Longitude (-180.0 to 180.0) = "; EvaluateVar gateway_longitude{,_d}
echo -n "d1 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc1{,_d}
echo -n "d2 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc2{,_d}
echo -n "w : URL (80 char max) = "; EvaluateVar gateway_url{,_d}
echo -n "fr : Find Route(s) = "; EvaluateVar gateway_find_route{,_d}
echo " Find Route(s) is a comma-separated list of common routing callsigns."
echo " These will be added to your local cache on boot-up."
echo
echo " APRS - Repeater/User position tracking"
echo -n "e : Enable APRS Tracking = "; EvaluateVar aprs_enable{,_d}
if [ -n "$em" ]; then
echo -n "h : APRS hostname = "; EvaluateVar aprs_host{,_d}
echo -n "ap : APRS TCP port number = "; EvaluateVar aprs_port{,_d}
echo -n "k : APRS Keep-alive interval (min) = "; EvaluateVar aprs_interval{,_d}
echo -n "af : APRS Filter (experimental) = "; EvaluateVar aprs_filter{,_d}
fi
EndMenu
if [[ "$key" == r* ]]; then SetBooleanValue gateway_header_regen "$value"
elif [[ "$key" == s* ]]; then SetBooleanValue gateway_send_qrgs_maps "$value"
elif [[ "$key" == i* ]]; then gateway_local_irc_ip="$value"
elif [[ "$key" == a* ]]; then gateway_ip="$value"
elif [[ "$key" == p* ]]; then gateway_port="$value"
elif [[ "$key" == tl* ]]; then gateway_gate2link="$value"
elif [[ "$key" == fl* ]]; then gateway_link2gate="$value"
elif [[ "$key" == ta* ]]; then gateway_gate2modema="$value"
elif [[ "$key" == tb* ]]; then gateway_gate2modemb="$value"
elif [[ "$key" == tc* ]]; then gateway_gate2modemc="$value"
elif [[ "$key" == fm* ]]; then gateway_modem2gate="$value"
elif [[ "$key" == la* ]]; then gateway_latitude="$value"
elif [[ "$key" == lo* ]]; then gateway_longitude="$value"
elif [[ "$key" == d1* ]]; then gateway_desc1="${value:0:20}"
elif [[ "$key" == d2* ]]; then gateway_desc2="${value:0:20}"
elif [[ "$key" == w* ]]; then gateway_url="${value:0:80}"
elif [[ "$key" == fr* ]]; then gateway_find_route="${value^^}"
elif [[ "$key" == e* ]]; then SetBooleanValue aprs_enable "$value"
elif [[ "$key" == h* ]]; then aprs_host="$value"
elif [[ "$key" == ap* ]]; then aprs_port="$value"
elif [[ "$key" == k* ]]; then aprs_interval="$value"
elif [[ "$key" == af* ]]; then aprs_filter="$value"
elif [[ "$key" == u* ]]; then
if [[ "$value" == r* ]]; then unset gateway_header_regen
elif [[ "$value" == s* ]]; then unset gateway_send_qrgs_maps
elif [[ "$value" == i* ]]; then unset gateway_local_irc_ip
elif [[ "$value" == a* ]]; then unset gateway_ip
elif [[ "$value" == p* ]]; then unset gateway_port
elif [[ "$value" == tl* ]]; then unset gateway_gate2link
elif [[ "$value" == ta* ]]; then unset gateway_gate2modema
elif [[ "$value" == tb* ]]; then unset gateway_gate2modemb
elif [[ "$value" == tc* ]]; then unset gateway_gate2modemc
elif [[ "$value" == fm* ]]; then unset gateway_modem2gate
elif [[ "$value" == la* ]]; then unset gateway_latitude
elif [[ "$value" == lo* ]]; then unset gateway_longitude
elif [[ "$value" == d1* ]]; then unset gateway_desc1
elif [[ "$value" == d2* ]]; then unset gateway_desc2
elif [[ "$value" == w* ]]; then unset gateway_url
elif [[ "$value" == fr* ]]; then unset gateway_find_route
elif [[ "$value" == e* ]]; then unset aprs_enable
elif [[ "$value" == h* ]]; then unset aprs_host
elif [[ "$value" == ap* ]]; then unset aprs_port
elif [[ "$value" == k* ]]; then unset aprs_interval
elif [[ "$value" == af* ]]; then unset aprs_filter
fi
fi
done
}
ModuleMenu () {
mod=module_${1}
if [[ $1 == a ]]; then
nmod='a'
elif [[ $1 == b ]]; then
nmod='b'
else
nmod='c'
fi
clear
if [ -z ${!mod} ]; then
echo
echo " Select a Module type"
echo
echo "1 : DVAP Dongle"
echo "2 : DVRPTR V1"
echo "3 : ICOM Terminal and Access Point Mode"
echo "4 : MMDVM Modem (like DVMega or ZUMspot) D-Star ONLY!"
echo "5 : MMDVMHost-based Sytem - Requires MMDVMHost"
echo
echo " Anything else will return without selecting"
echo
echo -n "Select Module Type : "
read key unused
if [[ "$key" == 1 ]]; then eval ${mod}=dvap
elif [[ "$key" == 2 ]]; then eval ${mod}=dvrptr
elif [[ "$key" == 3 ]]; then eval ${mod}=itap
elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvmmodem
elif [[ "$key" == 5 ]]; then eval ${mod}=mmdvmhost
else return
fi
fi
key=''
while [[ "$key" != q* ]]; do
clear
echo
echo " Module ${1^^} Menu ($mod=${!mod})"
echo
echo -n "ls : Link at startup (must be 8 chars) = "; EvaluateVar {${mod},module_x}_link_at_start
if [ -n "$em" ]; then
echo -n "ar : Auto relink in the case of a timeout = "; EvaluateVar {${mod},module_x}_auto_link
fi
echo -n "cs : Callsign (uses ircddb_login if empty) = "; EvaluateVar {${mod},module_x}_callsign
echo -n "ra : Range (in meters, 1 mile=1609.344 meters) = "; EvaluateVar {${mod},module_x}_range
echo -n "ag : Above ground level (in meters) = "; EvaluateVar {${mod},module_x}_agl
if [ -n "$em" ]; then
echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity
echo -n "ac : Send acknowledgment on each transmission = "; EvaluateVar {${mod},module_x}_acknowledge
echo -n "ad : acknowledgment delay (in msec) = "; EvaluateVar {${mod},module_x}_ack_delay
echo -n "pw : in msec, packet wait time (test for timeout) = "; EvaluateVar {${mod},module_x}_packet_wait
fi
if [[ "${!mod}" == 'dvap' ]]; then
echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},dvap}_frequency
echo -n "of : Offset in Hz = "; EvaluateVar {${mod},dvap}_offset
echo -n "po : Power (in dBm from -12 to 10) = "; EvaluateVar {${mod},dvap}_power
echo -n "sq : Squelch (in dBm from -128 to -45) = "; EvaluateVar {${mod},dvap}_squelch
echo -n "sn : Serial # (visible through the case) = "; EvaluateVar {${mod},dvap}_serial_number
elif [[ "${!mod}" == 'dvrptr' ]]; then
echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},dvrptr}_tx_frequency
echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},dvrtpr}_rx_frequency
echo -n "sn : Serial # (run once and look in log) = "; EvaluateVar {${mod},dvrptr}_serial_number
echo -n "rn : Callsign to turn RF on = "; EvaluateVar {${mod},dvrptr}_rf_on
echo -n "rf : Callsign to turn RF off = "; EvaluateVar {${mod},dvrptr}_rf_off
echo -n "rl : Receiver level = "; EvaluateVar {${mod},dvrptr}_rx_level
echo -n "du : Is duplex = "; EvaluateVar {${mod},dvrptr}_duplex
echo -n "td : Transmitter delay (in msec) for tx/rx switch = "; EvaluateVar {${mod},dvrptr}_tx_delay
echo -n "rq : # of 2 sec interval before system reset = "; EvaluateVar {${mod},dvrptr}_rqst_count
echo -n "ir : Inverse phase of receiver = "; EvaluateVar {${mod},dvrptr}_rx_invert
echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_tx_invert
elif [[ "${!mod}" == 'itap' ]]; then
echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},itap}_frequency
echo -n "dv : USB device path = "; EvaluateVar {${mod},itap}_device
elif [[ "${!mod}" == 'mmdvmhost' ]]; then
echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_tx_frequency
echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_rx_frequency
echo -n "ip : Internal IP address = "; EvaluateVar {${mod},mmdvmhost}_internal_ip
echo -n "gp : Gateway port number = "; EvaluateVar {${mod},mmdvmhost}_gateway_port
echo -n "lp : Local port number = "; EvaluateVar {${mod},mmdvmhost}_local_port
elif [[ "${!mod}" == 'mmdvmmodem' ]]; then
echo -n "dv : Device path = "; EvaluateVar {${mod},mmdvmmodem}_device
echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_tx_frequency
echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_rx_frequency
echo -n "to : Transmit offset, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_tx_offset
echo -n "r0 : Receive offset, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_rx_offset
echo -n "du : Is duplex = "; EvaluateVar {${mod},mmdvmmodem}_duplex
if [ -n "$em" ]; then
echo -n "it : Transmit invert = "; EvaluateVar {${mod},mmdvmmodem}_tx_invert
echo -n "ir : Receive invert = "; EvaluateVar {${mod},mmdvmmodem}_rx_invert
echo -n "pi : PTT invert = "; EvaluateVar {${mod},mmdvmmodem}_ptt_invert
echo -n "td : Transmit delay (in msec) for tx/rx switch = "; EvaluateVar {${mod},mmdvmmodem}_tx_delay
echo -n "tl : Transmit level (0-255) = "; EvaluateVar {${mod},mmdvmmodem}_tx_level
echo -n "rl : Receive level (0-255) = "; EvaluateVar {${mod},mmdvmmodem}_rx_level
fi
fi
echo "xx : Delete this module"
EndMenu
if [[ "$key" == ls* ]]; then
value="${value:0:8}"
eval ${mod}_link_at_start="'${value^^}'"
elif [[ "$key" == ac* ]]; then SetBooleanValue ${mod}_acknowledge "$value"
elif [[ "$key" == ad* ]]; then eval ${mod}_ack_delay="$value"
elif [[ "$key" == ag* ]]; then eval ${mod}_agl="$value"
elif [[ "$key" == ar* ]]; then SetBooleanValue ${mod}_auto_link "$value"
elif [[ "$key" == cs* ]]; then eval ${mod}_callsign="${value^^}"
elif [[ "$key" == du* ]]; then SetBooleanValue ${mod}_duplex "$value"
elif [[ "$key" == dv* ]]; then eval ${mod}_device="$value"
elif [[ "$key" == fr* ]]; then eval ${mod}_frequency="$value"
elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value"
elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value"
elif [[ "$key" == ir* ]]; then SetBooleanValue ${mod}_rx_invert "$value"
elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value"
elif [[ "$key" == it* ]]; then SetBooleanValue ${mod}_tx_invert "$value"
elif [[ "$key" == lp* ]]; then eval ${mod}_local_port="$value"
elif [[ "$key" == of* ]]; then eval ${mod}_offset="$value"
elif [[ "$key" == pi* ]]; then SetBooleanValue ${mod}_ptt_invert="$value"
elif [[ "$key" == po* ]]; then eval ${mod}_power="$value"
elif [[ "$key" == pw* ]]; then eval ${mod}_packet_wait="$value"
elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value"
elif [[ "$key" == rf* ]]; then eval ${mod}_rf_off="$value"
elif [[ "$key" == rl* ]]; then eval ${mod}_rx_level="$value"
elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$value"
elif [[ "$key" == ro* ]]; then eval ${mod}_rx_offset="$value"
elif [[ "$key" == rq* ]]; then eval ${mod}_rqst_count="$value"
elif [[ "$key" == rx* ]]; then eval ${mod}_rx_frequency="$value"
elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="${value^^}"
elif [[ "$key" == sq* ]]; then eval ${mod}_squelch="$value"
elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$value"
elif [[ "$key" == tl* ]]; then eval ${mod}_tx_level="$value"
elif [[ "$key" == to* ]]; then eval ${mod}_tx_offset="$value"
elif [[ "$key" == tx* ]]; then eval ${mod}_tx_frequency="$value"
elif [[ "$key" == xx* ]]; then
unset ${mod}_{link_at_start,callsign,{,rx_,tx_}frequency,offset,range,agl}
unset ${mod}_{inactivity,packet_wait,acknowledge,ack_delay,power,squelch,serial_number,rf_o{n,ff},{r,t}x_level}
unset ${mod}_{duplex,tx_delay,rqst_count,{tx,rx,ptt}_invert,device,internal_ip,{gateway,local}_port}
unset ${mod}_{tx_offset,rx_offset,auto_link}
unset ${mod}
return
elif [[ "$key" == u* ]]; then
if [[ "$value" == ac* ]]; then unset ${mod}_acknowledge
elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay
elif [[ "$value" == ar* ]]; then unset ${mod}_auto_link
elif [[ "$value" == ag* ]]; then unset ${mod}_agl
elif [[ "$value" == cs* ]]; then unset ${mod}_callsign
elif [[ "$value" == du* ]]; then unset ${mod}_duplex
elif [[ "$value" == dv* ]]; then unset ${mod}_device
elif [[ "$value" == fr* ]]; then unset ${mod}_frequency
elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port
elif [[ "$value" == in* ]]; then unset ${mod}_inactivity
elif [[ "$value" == ir* ]]; then unset ${mod}_rx_invert
elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip
elif [[ "$value" == it* ]]; then unset ${mod}_tx_invert
elif [[ "$value" == lp* ]]; then unset ${mod}_local_port
elif [[ "$value" == ls* ]]; then unset ${mod}_link_at_start
elif [[ "$value" == of* ]]; then unset ${mod}_offset
elif [[ "$value" == pi* ]]; then unset ${mod}_ppt_invert
elif [[ "$value" == po* ]]; then unset ${mod}_power
elif [[ "$value" == pw* ]]; then unset ${mod}_packet_wait
elif [[ "$value" == ra* ]]; then unset ${mod}_range
elif [[ "$value" == rf* ]]; then unset ${mod}_rf_off
elif [[ "$value" == rl* ]]; then unset ${mod}_rx_level
elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on
elif [[ "$value" == ro* ]]; then unset ${mod}_rx_offset
elif [[ "$value" == rq* ]]; then unset ${mod}_rqst_count
elif [[ "$value" == rx* ]]; then unset ${mod}_rx_frequency
elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number
elif [[ "$value" == sq* ]]; then unset ${mod}_squelch
elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay
elif [[ "$value" == tl* ]]; then unset ${mod}_tx_level
elif [[ "$value" == to* ]]; then unset ${mod}_tx_offset
elif [[ "$value" == tx* ]]; then unset ${mod}_tx_frequency
fi
fi
done
}
WriteCFGFile () {
local m p q outFile
if [ -z "$ircddb_login" ]; then
echo "You MUST set your ircddb login callsign (in the ircddb section)!"
read -p "Press <Enter> to continue: " ans
return
fi
outFile='./qn.cfg'
echo "# Created on `date`" > $outFile
# ircddb_ section
echo "ircddb_login='$ircddb_login'" >> $outFile
[ -z "${ircddb0_host+x}" ] || echo "ircddb0_host='${ircddb0_host}'" >> $outFile
[ -z "${ircddb0_port+x}" ] || echo "ircddb0_host=${ircddb0_port}" >> $outFile
[ -z "${ircddb0_password+x}" ] || echo "ircddb0_password='${ircddb0_password}'" >> $outFile
[ -z "${ircddb1_host+x}" ] || echo "ircddb1_host='${ircddb1_host}'" >> $outFile
[ -z "${ircddb1_port+x}" ] || echo "ircddb1_host=${ircddb1_port}" >> $outFile
[ -z "${ircddb1_password+x}" ] || echo "ircddb1_password='${ircddb1_password}'" >> $outFile
# module_?_ section
for m in a b c
do
p="module_$m"
if [ -n "${!p}" ]; then
echo "${p}=${!p}" >> $outFile
q=${p}_link_at_start; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_auto_link; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_callsign; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_range; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_inactivity; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_packet_wait; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_acknowledge; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_ack_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
if [[ "${!p}" == "dvap" ]]; then
q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_power; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_squelch; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
elif [[ "${!p}" == "dvrptr" ]]; then
q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_rf_on; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_rf_off; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_rx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_duplex; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rqst_count; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
elif [[ "${!p}" == "itap" ]]; then
q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
elif [[ "${!p}" == "mmdvmhost" ]]; then
q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_internal_ip; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_gateway_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_local_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
elif [[ "${!p}" == "mmdvmmodem" ]]; then
q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_duplex; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_ptt_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
fi
fi
done
# gateway_section
[ -z "${gateway_header_regen+x}" ] || echo "gateway_header_regen=${gateway_header_regen}" >> $outFile
[ -z "${gateway_send_qrgs_maps+x}" ] || echo "gateway_send_qrgs_maps=${gateway_send_qrgs_maps}" >> $outFile
[ -z "${gateway_local_irc_ip+x}" ] || echo "gateway_local_irc_ip='${gateway_local_irc_ip}'" >> $outFile
[ -z "${gateway_port+x}" ] || echo "gateway_port=${gateway_port}" >> $outFile
[ -z "${gateway_ip+x}" ] || echo "gateway_ip=${gateway_ip}" >> $outFile
[ -z "${gateway_tolink+x}" ] || echo "gateway_tolink=${gateway_tolink}" >> $outFile
[ -z "${gateway_fromlink+x}" ] || echo "gateway_fromlink=${gateway_fromlink}" >> $outFile
[ -z "${gateway_gate2modema+x}" ] || echo "gateway_gate2modema=${gateway_gate2modema}" >> $outFile
[ -z "${gateway_gate2modemb+x}" ] || echo "gateway_gate2modemb=${gateway_gate2modemb}" >> $outFile
[ -z "${gateway_gate2modemc+x}" ] || echo "gateway_gate2modemc=${gateway_gate2modemc}" >> $outFile
[ -z "${gateway_modem2gate+x}" ] || echo "gateway_modem2gate=${gateway_modem2gate}" >> $outFile
[ -z "${gateway_latitude+x}" ] || echo "gateway_latitude=${gateway_latitude}" >> $outFile
[ -z "${gateway_longitude+x}" ] || echo "gateway_longitude=${gateway_longitude}" >> $outFile
[ -z "${gateway_desc1+x}" ] || echo "gateway_desc1='${gateway_desc1}'" >> $outFile
[ -z "${gateway_desc2+x}" ] || echo "gateway_desc2='${gateway_desc2}'" >> $outFile
[ -z "${gateway_url+x}" ] || echo "gateway_url='${gateway_url}'" >> $outFile
[ -z "${gateway_find_route+x}" ] || echo "gateway_find_route='${gateway_find_route}'" >> $outFile
# arps_ section
[ -z "${aprs_enable+x}" ] || echo "aprs_enable=${aprs_enable}" >> $outFile
[ -z "${aprs_host+x}" ] || echo "aprs_host='${aprs_host}'" >> $outFile
[ -z "${aprs_port+x}" ] || echo "aprs_port=${aprs_port}" >> $outFile
[ -z "${aprs_interval+x}" ] || echo "aprs_interval=${aprs_interval}" >> $outFile
[ -z "${aprs_filter+x}" ] || echo "aprs_filter='${aprs_filter}'" >> $outFile
# link_ section
[ -z "${link_admin+x}" ] || echo "link_admin='${link_admin}'" >> $outFile
[ -z "${link_link_unlink+x}" ] || echo "link_link_unlink='${link_link_unlink}'" >> $outFile
[ -z "${link_no_link_unlink+x}" ] || echo "link_no_link_unlink='${link_no_link_unlink}'" >> $outFile
[ -z "${link_incoming_ip+x}" ] || echo "link_incoming_ip='${link_incoming_ip}'" >> $outFile
[ -z "${link_ref_port+x}" ] || echo "link_ref_port=${link_ref_port}" >> $outFile
[ -z "${link_xrf_port+x}" ] || echo "link_xrf_port=${link_xrf_port}" >> $outFile
[ -z "${link_dcs_port+x}" ] || echo "link_dcs_port=${link_dcs_port}" >> $outFile
[ -z "${link_announce+x}" ] || echo "link_announce=${link_announce}" >> $outFile
[ -z "${link_acknowledge+x}" ] || echo "link_acknowledge=${link_acknowledge}" >> $outFile
[ -z "${link_max_dongles+x}" ] || echo "link_max_dongles=${link_max_dongles}" >> $outFile
# log_ section
[ -z "${log_qso+x}" ] || echo "log_qso=${log_qso}" >> $outFile
[ -z "${log_irc+x}" ] || echo "log_irc=${log_irc}" >> $outFile
[ -z "${log_dtmf+x}" ] || echo "log_dtmf=${log_dtmf}" >> $outFile
[ -z "${log_debug+x}" ] || echo "log_debug=${log_debug}" >> $outFile
# dplus_ section
[ -z "${dplus_authorize+x}" ] || echo "dplus_authorize=${dplus_authorize}" >> $outFile
[ -z "${dplus_priority+x}" ] || echo "dplus_priority=${dplus_priority}" >> $outFile
[ -z "${dplus_ref_login+x}" ] || echo "dplus_ref_login='${dplus_ref_login}'" >> $outFile
[ -z "${dplus_use_repeaters+x}" ] || echo "dplus_use_repeaters=${dplus_use_repeaters}" >> $outFile
[ -z "${dplus_use_reflectors+x}" ] || echo "dplus_use_reflectors=${dplus_use_reflectors}" >> $outFile
# file_ section
[ -z "${file_gwys+x}" ] || echo "file_gwys='${file_gwys}'" >> $outFile
[ -z "${file_dtmf+x}" ] || echo "file_dtmf='${file_dtmf}'" >> $outFile
[ -z "${file_status+x}" ] || echo "file_status='${file_status}'" >> $outFile
[ -z "${file_echotest+x}" ] || echo "file_echotest='${file_echotest}'" >> $outFile
[ -z "${file_qnvoicefile+x}" ] || echo "file_qnvoicefile='${file_qnvoicefile}'" >> $outFile
[ -z "${file_announce_dir+x}" ] || echo "file_announce_dir='${file_announce_dir}'" >> $outFile
# timing_ section
[ -z "${timing_timeout_echo+x}" ] || echo "timing_timeout_echo=${timing_timeout_echo}" >> $outFile
[ -z "${timing_timeout_remote_g2+x}" ] || echo "timing_timeout_remote_g2=${timing_timeout_remote_g2}" >> $outFile
[ -z "${timing_timeout_voicemail+x}" ] || echo "timing_timeout_voicemail=${timing_timeout_voicemail}" >> $outFile
[ -z "${timing_timeout_local_rptr+x}" ] || echo "timing_timeout_local_rptr=${timing_timeout_local_rptr}" >> $outFile
[ -z "${timing_play_wait+x}" ] || echo "timing_play_wait=${timing_play_wait}" >> $outFile
[ -z "${timing_play_delay+x}" ] || echo "timing_play_delay=${timing_play_delay}" >> $outFile
clear
cat $outFile
echo
read -p "File written! Press <Enter> to continue: " ans
}
# Execution starts here!
# source files
if [ -e ./defaults ]; then
source ./defaults
else
echo 'Error: ./defaults not found!'
exit 1
fi
if [ -e ./qn.cfg ]; then
source ./qn.cfg
else
echo 'No configuration file found...'
sleep 1
fi
# process arguments
if [[ "$1" == ex* ]]; then
em="expertMode"
fi
# main loop
while [[ "$ans" != q* ]]
do
clear
echo
echo " QnConfig Main Menu V#190424"
echo
echo -n "a : Module A - "; if [ -z $module_a ]; then echo "<EMPTY>"; else echo "${module_a^^}"; fi
echo -n "b : Module B - "; if [ -z $module_b ]; then echo "<EMPTY>"; else echo "${module_b^^}"; fi
echo -n "c : Module C - "; if [ -z $module_c ]; then echo "<EMPTY>"; else echo "${module_c^^}"; fi
echo "i : IRCDDB Menu - login = '${ircddb_login}'"
echo "g : Gateway/APRS Menu - location info and route cache"
echo "l : Link/D-Plus Menu - linking access"
if [ -n "$em" ]; then
echo "d : Debugging/Files/Timings - miscellaneous parameters"
else
echo "d : Debugging - additional log entries"
fi
echo
if [ -n $module_a ] || [ -n $module_b ] || [ -n $module_c ] && [ -n $ircddb_login ]; then
echo "w : Write qn.cfg configuration file (overwrites any existing file)"
echo
fi
read -p "q to quit: " ans
if [[ "$ans" == a* ]]; then ModuleMenu a
elif [[ "$ans" == b* ]]; then ModuleMenu b
elif [[ "$ans" == c* ]]; then ModuleMenu c
elif [[ "$ans" == i* ]]; then IrcddbMenu
elif [[ "$ans" == g* ]]; then GateMenu
elif [[ "$ans" == l* ]]; then LinkMenu
elif [[ "$ans" == d* ]]; then FileMenu
elif [[ "$ans" == w* ]]; then WriteCFGFile
fi
done
[ -e qn.cfg ] && cat qn.cfg
exit 0

@ -12,9 +12,26 @@
# Example: D00617 will link local module to DCS006 Q
# Example: *00103 will link local module to REF001 C
# Example: 0 or 00 will report status of the link, " I"
# Example: ##08 will execute the exec_H.sh script (shutdown the system)
# Please note that scripts exec_[0-9].sh are not accessible from DTMF.
# We set this to spaces, it will be set later
GetLetter () {
local i
if [[ $1 == +([0-9]) ]]; then
i=`expr $1 - 1`
if [ $i -ge 0 ] && [ $i -lt 26 ]; then
LETTER=${LETTERS[$i]}
return
fi
fi
LETTER=$BAD
}
LUSER=" "
LETTERS=( {A..Z} )
BAD='bad'
cd /tmp
echo started at `date`
@ -25,101 +42,62 @@ do
do
echo found file $i at `date`
LOCAL_BAND=${i:0:1}
if [[ "$LOCAL_BAND" = "A" ]] || [[ "$LOCAL_BAND" = "B" ]] || [[ "$LOCAL_BAND" = "C" ]] ; then
if [[ "$LOCAL_BAND" == 'A' ]] || [[ "$LOCAL_BAND" == 'B' ]] || [[ "$LOCAL_BAND" == 'C' ]]; then
CMD=`head -n 1 $i 2>/dev/null`
LUSER=`tail -n 1 $i 2>/dev/null`
echo "... with these contents: " $CMD " " $LUSER
if [[ "$CMD" = "#" ]] ; then
if [[ "$CMD" == '#' ]]; then
echo Unlinking local band $LOCAL_BAND requested by $LUSER
qnremote ${LOCAL_BAND} "$LUSER" U >/dev/null 2>&1
echo
elif [[ "$CMD" = "0" ]] || [[ "$CMD" = "00" ]] ; then
elif [[ "$CMD" == '0' ]] || [[ "$CMD" == '00' ]]; then
echo Link Status on local band $LOCAL_BAND requested by $LUSER
qnremote ${LOCAL_BAND} "$LUSER" I >/dev/null 2>&1
echo
elif [[ "$CMD" = "**" ]] ; then
elif [[ "$CMD" == '**' ]]; then
echo Load Hosts on local band $LOCAL_BAND requested by $LUSER
qnremote ${LOCAL_BAND} "$LUSER" L >/dev/null 2>&1
qnremote ${LOCAL_BAND} "$LUSER" F >/dev/null 2>&1
else
LEN=${#CMD}
if [[ "$LEN" = "6" ]] ; then
PFX=${CMD:0:1}
REMOTE_NODE=${CMD:1:3}
REMOTE_BAND=${CMD:4:2}
if [[ "$REMOTE_BAND" = "01" ]] ; then
REMOTE_BAND=A
elif [[ "$REMOTE_BAND" = "02" ]] ; then
REMOTE_BAND=B
elif [[ "$REMOTE_BAND" = "03" ]] ; then
REMOTE_BAND=C
elif [[ "$REMOTE_BAND" = "04" ]] ; then
REMOTE_BAND=D
elif [[ "$REMOTE_BAND" = "05" ]] ; then
REMOTE_BAND=E
elif [[ "$REMOTE_BAND" = "06" ]] ; then
REMOTE_BAND=F
elif [[ "$REMOTE_BAND" = "07" ]] ; then
REMOTE_BAND=G
elif [[ "$REMOTE_BAND" = "08" ]] ; then
REMOTE_BAND=H
elif [[ "$REMOTE_BAND" = "09" ]] ; then
REMOTE_BAND=I
elif [[ "$REMOTE_BAND" = "10" ]] ; then
REMOTE_BAND=J
elif [[ "$REMOTE_BAND" = "11" ]] ; then
REMOTE_BAND=K
elif [[ "$REMOTE_BAND" = "12" ]] ; then
REMOTE_BAND=L
elif [[ "$REMOTE_BAND" = "13" ]] ; then
REMOTE_BAND=M
elif [[ "$REMOTE_BAND" = "14" ]] ; then
REMOTE_BAND=N
elif [[ "$REMOTE_BAND" = "15" ]] ; then
REMOTE_BAND=O
elif [[ "$REMOTE_BAND" = "16" ]] ; then
REMOTE_BAND=P
elif [[ "$REMOTE_BAND" = "17" ]] ; then
REMOTE_BAND=Q
elif [[ "$REMOTE_BAND" = "18" ]] ; then
REMOTE_BAND=R
elif [[ "$REMOTE_BAND" = "19" ]] ; then
REMOTE_BAND=S
elif [[ "$REMOTE_BAND" = "20" ]] ; then
REMOTE_BAND=T
elif [[ "$REMOTE_BAND" = "21" ]] ; then
REMOTE_BAND=U
elif [[ "$REMOTE_BAND" = "22" ]] ; then
REMOTE_BAND=V
elif [[ "$REMOTE_BAND" = "23" ]] ; then
REMOTE_BAND=W
elif [[ "$REMOTE_BAND" = "24" ]] ; then
REMOTE_BAND=X
elif [[ "$REMOTE_BAND" = "25" ]] ; then
REMOTE_BAND=Y
elif [[ "$REMOTE_BAND" = "26" ]] ; then
REMOTE_BAND=Z
if [ ${#CMD} -eq 4 ] && [[ ${CMD:0:2} == '##' ]]; then
GetLetter ${CMD:2:2}
if [[ "$LETTER" == "$BAD" ]]; then
echo "bad script letter index: '${CMD:2:2}'"
qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad DTMF CMD"
else
REMOTE_BAND=Z
qnremote $LOCAL_BAND $LUSER ${LETTER}X >/dev/null 2>&1
fi
elif [ ${#CMD} -eq 6 ]; then
if [[ "$PFX" = "B" ]] ; then
PFX=${CMD:0:1}
if [[ "$PFX" = 'B' ]]; then
RMT=XRF
elif [[ "$PFX" = "D" ]] ; then
elif [[ "$PFX" = 'D' ]]; then
RMT=DCS
elif [[ "$PFX" = "*" ]] ; then
elif [[ "$PFX" = '*' ]]; then
RMT=REF
else
RMT=garbage
RMT=$BAD
fi
REMOTE_NODE=${CMD:1:3}
if [[ $REMOTE_NODE != +([0-9]) ]]; then
REMOTE_NODE=$BAD
fi
if [[ "$RMT" == "garbage" ]] ; then
echo bad value in prefix
GetLetter ${CMD:4:2}
REMOTE_BAND=$LETTER
if [[ "$RMT" == "$BAD" ]] || [[ "$REMOTE_NODE" == "$BAD" ]] || [[ "$REMOTE_BAND" == "$BAD" ]]; then
echo "Bad link command: '$CMD'"
qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad Link CMD"
else
echo linking local band $LOCAL_BAND to remote node ${RMT}${REMOTE_NODE} $REMOTE_BAND requested by $LUSER
qnremote ${LOCAL_BAND} "$LUSER" ${RMT}${REMOTE_NODE}${REMOTE_BAND}L >/dev/null 2>&1
echo
fi
else
echo "Bad command: '$CMD'"
qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad CMD"
fi
fi
else
@ -131,4 +109,3 @@ do
done
exit 0

@ -1,14 +0,0 @@
#!/bin/bash
wget ftp://dschost1.w6kd.com/DExtra_Hosts.txt || wget ftp://dschost2.w6kd.com/DExtra_Hosts.txt
wget ftp://dschost1.w6kd.com/DPlus_Hosts.txt || wget ftp://dschost2.w6kd.com/DPlus_Hosts.txt
wget ftp://dschost1.w6kd.com/DCS_Hosts.txt || wget ftp://dschost2.w6kd.com/DCS_Hosts.txt
/bin/rm -f gwys.txt
echo "# Downloaded from dschost1.w6kd.com `date`" > gwys.txt
awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt
awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt
awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt
/bin/rm -f D{Extra,Plus,DSC}_Hosts.txt

@ -1,10 +1,10 @@
[Unit]
Description=MMDVMHost
After=systemd-user-session.service
After=systemd-user-session.service qnrelay.service
[Service]
Type=simple
ExecStart=/usr/local/bin/MMDVMHost /usr/local/etc/MMDVM.qn
ExecStart=/usr/local/bin/XXX /usr/local/etc/YYY.qn
Restart=always
[Install]

@ -1,10 +1,10 @@
[Unit]
Description=QnetDVAP
After=systemd-user-session.service
After=systemd-user-session.service qngateway.service
[Service]
Type=simple
ExecStart=/usr/local/bin/qndvap /usr/local/etc/qn.cfg
ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg
Restart=always
[Install]

@ -1,10 +1,10 @@
[Unit]
Description=QnetDVRPTR
After=systemd-user-session.service
After=systemd-user-session.service qngateway.service
[Service]
Type=simple
ExecStart=/usr/local/bin/qndvrptr /usr/local/etc/qn.cfg
ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg
Restart=always
[Install]

@ -1,10 +1,12 @@
[Unit]
Description=QnetGateway
After=systemd-user-session.service
Requires=network.target
After=systemd-user-session.service network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/qngateway /usr/local/etc/qn.cfg
Restart=always
[Install]
WantedBy=multi-user.target

@ -0,0 +1,11 @@
[Unit]
Description=QnetITAP
After=systemd-user-session.service qngateway.service
[Service]
Type=simple
ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg
Restart=always
[Install]
WantedBy=multi-user.target

@ -1,10 +1,12 @@
[Unit]
Description=QnetLink
After=systemd-user-session.service
Requires=network.target
After=systemd-user-session.service network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/qnlink /usr/local/etc/qn.cfg
Restart=always
[Install]
WantedBy=multi-user.target

@ -0,0 +1,11 @@
[Unit]
Description=QnetModem
After=systemd-user-session.service qngateway.service
[Service]
Type=simple
ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg
Restart=always
[Install]
WantedBy=multi-user.target

@ -1,10 +1,11 @@
[Unit]
Description=QnetRelay
After=systemd-user-session.service
After=systemd-user-session.service qngateway.service
[Service]
Type=simple
ExecStart=/usr/local/bin/qnrelay /usr/local/etc/qn.cfg
ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg
Restart=always
[Install]
WantedBy=multi-user.target

@ -1,6 +0,0 @@
// version strings must be 55 characters or less!
#define IRCDDB_VERSION "linux-qngateway-6.1.0"
#define LINK_VERSION "5.2.0"
#define DVAP_VERSION "linux-qndvap-5.1.1"
#define DVRPTR_VERSION "linux-qndvrptr-5.1.0"
#define MMDVM_VERSION "mmdvm-qnmodem-0.1.0"
Loading…
Cancel
Save

Powered by TurnKey Linux.