commit
3f71c3e683
@ -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);
|
||||
};
|
||||
@ -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
@ -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;
|
||||
};
|
||||
@ -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.
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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,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…
Reference in new issue