initial qnmodem commit

pull/12/head
Tom Early 7 years ago
parent 938e2bef49
commit 3f8d2da618

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

@ -37,7 +37,7 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp)
OBJS = $(SRCS:.cpp=.o) OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) DEPS = $(SRCS:.cpp=.d)
ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap qnmodem
BASE_PROGRAMS=qngateway qnlink qnremote qnvoice BASE_PROGRAMS=qngateway qnlink qnremote qnvoice
all : $(ALL_PROGRAMS) all : $(ALL_PROGRAMS)
@ -46,6 +46,7 @@ relay : qnrelay
dvap : qndvap dvap : qndvap
dvrptr : qndvrptr dvrptr : qndvrptr
itap : qnitap itap : qnitap
modem : qnmodem
qngateway : QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS) qngateway : QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS)
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread
@ -59,6 +60,9 @@ qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o
qnitap : QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o qnitap : QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qnmodem : QnetModem.o Random.o UnixDgramSocket.o QnetConfigure.o
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS)
qndvap : QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) qndvap : QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS)
g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread
@ -127,6 +131,14 @@ installitap : qnitap
systemctl daemon-reload systemctl daemon-reload
systemctl start qnitap$(MODULE).service systemctl start qnitap$(MODULE).service
installmodem : qnmodem
######### QnetModem #########
/bin/ln -f qnmodem $(BINDIR)/qnmodem$(MODULE)
sed -e "s/XXX/qnmodem$(MODULE)/" system/qnmodem.service > $(SYSDIR)/qnmodem$(MODULE).service
systemctl enable qnmodem$(MODULE).service
systemctl daemon-reload
systemctl start qnmodem$(MODULE).service
installdvap : qndvap installdvap : qndvap
######### QnetDVAP ######### ######### QnetDVAP #########
/bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE) /bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE)
@ -188,6 +200,14 @@ uninstallmmdvm :
/bin/rm -f $(CFGDIR)/MMDVM$(MODULE).qn /bin/rm -f $(CFGDIR)/MMDVM$(MODULE).qn
sudo systemctl daemon-reload sudo systemctl daemon-reload
uninstallmodem :
######### QnetModem #########
systemctl stop qnmodem$(MODULE).service
systemctl disable qnmodem$(MODULE).service
/bin/rm -f $(SYSDIR)/qnmodem$(MODULE).service
/bin/rm -f $(BINDIR)/qnmodem$(MODULE)
systemctl daemon-reload
uninstallitap : uninstallitap :
######### QnetITAP ######### ######### QnetITAP #########
systemctl stop qnitap$(MODULE).service systemctl stop qnitap$(MODULE).service

@ -27,7 +27,7 @@
#include "UnixDgramSocket.h" #include "UnixDgramSocket.h"
#include "QnetConfigure.h" #include "QnetConfigure.h"
#define DVRPTR_VERSION "QnetDVRPTR-6.0.2" #define DVRPTR_VERSION "QnetDVRPTR-6.0.3"
#define BAUD B115200 #define BAUD B115200
#define CALL_SIZE 8 #define CALL_SIZE 8
@ -1897,8 +1897,8 @@ static bool read_config(const char *cfgFile)
cfg.GetValue(path+"_rqst_count", type, RQST_COUNT, 6, 20); cfg.GetValue(path+"_rqst_count", type, RQST_COUNT, 6, 20);
cfg.GetValue(path+"_inverse_rx", type, RX_Inverse); cfg.GetValue(path+"_rx_invert", type, RX_Inverse);
cfg.GetValue(path+"_inverse_tx", type, TX_Inverse); cfg.GetValue(path+"_tx_invert", type, TX_Inverse);
path.assign("timing_"); path.assign("timing_");
cfg.GetValue(path+"timeout_remote_g2", estr, REMOTE_TIMEOUT, 1, 10); cfg.GetValue(path+"timeout_remote_g2", estr, REMOTE_TIMEOUT, 1, 10);

@ -57,7 +57,7 @@
#include "QnetConfigure.h" #include "QnetConfigure.h"
#include "QnetGateway.h" #include "QnetGateway.h"
#define IRCDDB_VERSION "QnetGateway-8.1.0" #define IRCDDB_VERSION "QnetGateway-8.1.1"
extern void dstar_dv_init(); extern void dstar_dv_init();
extern int dstar_dv_decode(const unsigned char *d, int data[3]); extern int dstar_dv_decode(const unsigned char *d, int data[3]);
@ -236,14 +236,11 @@ bool CQnetGateway::read_config(char *cfgFile)
rptr.mod[m].defined = false; rptr.mod[m].defined = false;
} else { } else {
printf("Found Module: %s = '%s'\n", path.c_str(), type.c_str()); printf("Found Module: %s = '%s'\n", path.c_str(), type.c_str());
if (0 == type.compare("dvap")) { if (0 == type.compare("dvap")) { rptr.mod[m].package_version = "QnetDVAP";
rptr.mod[m].package_version = "QnetDVAP"; } else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version = "QnetDVRPTR";
} else if (0 == type.compare("dvrptr")) { } else if (0 == type.compare("mmdvmhost")) { rptr.mod[m].package_version = "QnetRelay";
rptr.mod[m].package_version = "QnetDVRPTR"; } else if (0 == type.compare("mmdvmmodem")) { rptr.mod[m].package_version = "QnetModem";
} else if (0 == type.compare("mmdvm")) { } else if (0 == type.compare("itap")) { rptr.mod[m].package_version = "QnetITAP";
rptr.mod[m].package_version = "QnetRelay";
} else if (0 == type.compare("itap")) {
rptr.mod[m].package_version = "QnetITAP";
} else { } else {
printf("module type '%s' is invalid\n", type.c_str()); printf("module type '%s' is invalid\n", type.c_str());
return true; return true;
@ -251,8 +248,19 @@ bool CQnetGateway::read_config(char *cfgFile)
rptr.mod[m].defined = true; rptr.mod[m].defined = true;
path.append(1, '_'); path.append(1, '_');
cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0e12); if (cfg.KeyExists(path+"tx_frequency")) {
cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); cfg.GetValue(path+"tx_frequency", type, rptr.mod[m].frequency, 0.0, 6.0E9);
double rx_freq;
cfg.GetValue(path+"rx_frequency", type, rx_freq, 0.0, 6.0E9);
if (0.0 == rx_freq)
rx_freq = rptr.mod[m].frequency;
rptr.mod[m].offset = rx_freq - rptr.mod[m].frequency;
} else if (cfg.KeyExists(path+"frequency")) {
cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0E9);
rptr.mod[m].offset = 0.0;
} else {
rptr.mod[m].frequency = rptr.mod[m].offset = 0.0;
}
cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0);
cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0);

@ -0,0 +1,797 @@
/*
* 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 "QnetTypeDefs.h"
#include "QnetConfigure.h"
#define MODEM_VERSION "QnetModem-0.0.0"
#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)
, COUNTER(0)
, dstarSpace(0U)
, g2_is_active(false)
{
}
CQnetModem::~CQnetModem()
{
}
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));
MODEM_RESPONSE resp = GetModemData(&frame.start, sizeof(SVERSION));
if (resp == VERSION_RESPONSE && frame.length > 14U) {
frame.version[frame.length-1U] = '\0'; // just to make sure!
if (0 == memcmp(frame.version, "MMDVM ", 6U))
hardwareType = HWT_MMDVM;
else if (0 == memcmp(frame.version, "DVMEGA", 6U))
hardwareType = HWT_DVMEGA;
else if (0 == memcmp(frame.version, "ZUMspot", 7U))
hardwareType = HWT_MMDVM_ZUMSPOT;
else if (0 == memcmp(frame.version, "MMDVM_HS_Hat", 12U))
hardwareType = HWT_MMDVM_HS_HAT;
else if (0 == memcmp(frame.version, "MMDVM_HS_Dual_Hat", 17U))
hardwareType = HWT_MMDVM_HS_DUAL_HAT;
else if (0 == memcmp(frame.version, "Nano_hotSPOT", 12U))
hardwareType = HWT_NANO_HOTSPOT;
else if (0 == memcmp(frame.version, "Nano_DV", 7U))
hardwareType = HWT_NANO_DV;
else if (0 == memcmp(frame.version, "MMDVM_HS-", 9U))
hardwareType = HWT_MMDVM_HS;
else {
hardwareType = HWT_UNKNOWN;
}
printf("MMDVM protocol version: %u, Modem: %s", frame.protocol, frame.version);
return false;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
}
fprintf(stderr, "Unable to read the firmware version after six attempts");
return true;
}
bool CQnetModem::SetFrequency()
{
uint32_t pocsagFrequency = 433000000U;
SMODEM frame;
frame.start = FRAME_START;
frame.type = TYPE_FREQ;
if (hardwareType == HWT_DVMEGA)
frame.length = 12U;
else {
frame.frequency.level = 0x0U;
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 ACK_RESPONSE:
got_ack = true;
break;
case NACK_RESPONSE:
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)RX_FREQUENCY, rx_frequency, (uint32_t)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;
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 ACK_RESPONSE:
got_ack = true;
break;
case NACK_RESPONSE:
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;
}
MODEM_RESPONSE CQnetModem::GetModemData(unsigned char *buf, unsigned int size)
{
if (size < 4U) {
fprintf(stderr, "Buffer size, %u is too small\n", size);
return ERROR_RESPONSE;
}
// 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 ERROR_RESPONSE;
} else if (ret == 0)
return TIMEOUT_RESPONSE;
else if (buf[0] != FRAME_START)
return TIMEOUT_RESPONSE;
//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 ERROR_RESPONSE;
} else if (ret == 0) {
return(TIMEOUT_RESPONSE);
} else if ((unsigned int)buf[1] > size) {
fprintf(stderr, "Error, buffer is %u bytes, but returned frame is %u bytes\n", size, (unsigned int)buf[1]);
return ERROR_RESPONSE;
}
// 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 ERROR_RESPONSE;
} else if (ret == 0)
return(TIMEOUT_RESPONSE);
// 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 ERROR_RESPONSE;
}
offset += ret;
}
switch (buf[2]) {
case TYPE_ACK:
return ACK_RESPONSE;
case TYPE_NACK:
return NACK_RESPONSE;
case TYPE_HEADER:
return HEADER_RESPONSE;
case TYPE_DATA:
return DATA_RESPONSE;
case TYPE_LOST:
return LOST_RESPONSE;
case TYPE_EOT:
return EOT_RESPONSE;
case TYPE_VERSION:
return VERSION_RESPONSE;
case TYPE_STATUS:
return STATUS_RESPONSE;
default:
return ERROR_RESPONSE;
};
}
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 DATA_RESPONSE:
case HEADER_RESPONSE:
case EOT_RESPONSE:
case LOST_RESPONSE:
if (ProcessModem(frame))
keep_running = false;
break;
case STATUS_RESPONSE:
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)) {
unsigned char buf[100];
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, "DSTR", 4)) {
//printf("read %d bytes from QnetGateway\n", (int)len);
if (ProcessGateway(len, buf))
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)) {
queue.pop();
SendToModem(cframe.data());
dstarSpace -= (type==TYPE_HEADER) ? 4U : 1U;
}
}
if (dstarSpace<4 || statusTimer.time()>0.1) {
// request a status update every 100 milliseconds or when needed
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 ptr = 0;
ssize_t length = buf[1];
while ((ssize_t)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;
}
return length;
}
bool CQnetModem::ProcessGateway(const int len, const unsigned char *raw)
{
if (29==len || 58==len) { //here is dstar data
SDSTR dstr;
memcpy(dstr.pkt_id, raw, len); // transfer raw data to SDSTR struct
SMODEM frame; // destination
frame.start = FRAME_START;
if (58 == len) { // write a Header packet
frame.length = 44U;
frame.type = TYPE_HEADER;
memcpy(frame.header.flag, dstr.vpkt.hdr.flag, 3);
if (RPTR_MOD == dstr.vpkt.hdr.r2[7]) {
memcpy(frame.header.r1, dstr.vpkt.hdr.r2, 8);
memcpy(frame.header.r2, dstr.vpkt.hdr.r1, 8);
} else {
memcpy(frame.header.r1, dstr.vpkt.hdr.r1, 8);
memcpy(frame.header.r2, dstr.vpkt.hdr.r2, 8);
}
memcpy(frame.header.ur, dstr.vpkt.hdr.ur, 8);
memcpy(frame.header.my, dstr.vpkt.hdr.my, 8);
memcpy(frame.header.nm, dstr.vpkt.hdr.nm, 4);
memcpy(frame.header.pfcs, dstr.vpkt.hdr.pfcs, 2);
queue.push(CFrame(&frame.start));
PacketWait.start();
g2_is_active = true;
if (LOG_QSO)
printf("Queued to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MODEM_DEVICE.c_str(), frame.header.ur, frame.header.r1, frame.header.r2, frame.header.my, frame.header.nm);
} else { // write a voice data packet
if (g2_is_active) {
if (dstr.vpkt.ctrl & 0x40U) {
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, dstr.vpkt.vasd.voice, 12);
}
queue.push(CFrame(&frame.start));
PacketWait.start();
}
}
} else
printf("DEBUG: ProcessGateway: unusual packet size read len=%d\n", len);
return false;
}
bool CQnetModem::ProcessModem(const SMODEM &frame)
{
static short stream_id = 0U;
static unsigned char ctrl = 0U;
// create a stream id if this is a header
if (frame.type == TYPE_HEADER)
stream_id = random.NewStreamID();
SDSTR dstr; // destination
// sets most of the params
memcpy(dstr.pkt_id, "DSTR", 4);
dstr.counter = htons(COUNTER++);
dstr.flag[0] = 0x73;
dstr.flag[1] = 0x12;
dstr.flag[2] = 0x0;
dstr.vpkt.icm_id = 0x20;
dstr.vpkt.dst_rptr_id = 0x0;
dstr.vpkt.snd_rptr_id = 0x1;
dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3);
dstr.vpkt.streamid = htons(stream_id);
if (frame.type == TYPE_HEADER) { // header
ctrl = 0U;
dstr.remaining = 0x30;
dstr.vpkt.ctrl = 0x80;
memcpy(dstr.vpkt.hdr.flag, frame.header.flag, 3);
memcpy(dstr.vpkt.hdr.r1, frame.header.r1, 8);
memcpy(dstr.vpkt.hdr.r2, frame.header.r2, 8);
memcpy(dstr.vpkt.hdr.ur, frame.header.ur, 8);
dstr.vpkt.hdr.flag[0] &= ~0x40U; // clear this bit
memcpy(dstr.vpkt.hdr.my, frame.header.my, 8);
memcpy(dstr.vpkt.hdr.nm, frame.header.nm, 4);
memcpy(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs, 2);
if (58 != Modem2Gate.Write(dstr.pkt_id, 58)) {
printf("ERROR: ProcessModem: Could not write gateway header packet\n");
return true;
}
if (LOG_QSO)
printf("Sent DSTR to gateway, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm);
} else if (frame.type==TYPE_DATA || frame.type==TYPE_EOT || frame.type==TYPE_LOST) { // ambe
dstr.remaining = 0x16;
dstr.vpkt.ctrl = ctrl++;
if (ctrl >= 21U)
ctrl = 0U;
if (frame.type==TYPE_DATA) {
memcpy(dstr.vpkt.vasd.voice, frame.voice.ambe, 12);
} else {
const unsigned char silence[12] = { 0x4EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x70,0x4FU,0x93U };
memcpy(dstr.vpkt.vasd.voice, silence, 12);
dstr.vpkt.ctrl &= 0x40U;
}
if (29 != Modem2Gate.Write(dstr.pkt_id, 29)) {
printf("ERROR: ProcessModem: Could not write gateway voice packet\n");
return true;
}
if (LOG_QSO && (dstr.vpkt.ctrl & 0x40)) {
if (frame.type == TYPE_EOT)
printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid));
else
printf("Sent lost end of streamid=%04x\n", ntohs(dstr.vpkt.streamid));
}
}
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+"_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);
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,246 @@
/*
* 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"
#define CALL_SIZE 8
#define IP_SIZE 15
enum MODEM_RESPONSE {
ACK_RESPONSE,
NACK_RESPONSE,
TIMEOUT_RESPONSE,
ERROR_RESPONSE,
HEADER_RESPONSE,
DATA_RESPONSE,
LOST_RESPONSE,
EOT_RESPONSE,
STATUS_RESPONSE,
VERSION_RESPONSE
};
enum HARDWARE_TYPE {
HWT_MMDVM,
HWT_DVMEGA,
HWT_MMDVM_ZUMSPOT,
HWT_MMDVM_HS_HAT,
HWT_MMDVM_HS_DUAL_HAT,
HWT_NANO_HOTSPOT,
HWT_NANO_DV,
HWT_MMDVM_HS,
HWT_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[252];
} 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() {}
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 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;
};
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 short COUNTER;
unsigned char dstarSpace;
bool g2_is_active;
// functions
bool Initialize(const char *cfgfile);
static void SignalCatch(const int signum);
bool ProcessGateway(const int len, const unsigned char *raw);
bool ProcessModem(const SMODEM &frame);
int OpenModem();
int SendToModem(const unsigned char *buf);
MODEM_RESPONSE GetModemData(unsigned char *buf, unsigned int size);
bool GetVersion();
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;
// parameters
HARDWARE_TYPE hardwareType;
int serfd;
// helpers
CRandom random;
CTimer PacketWait;
// unix sockets
std::string modem2gate, gate2modem;
CUnixDgramWriter Modem2Gate;
CUnixDgramReader Gate2Modem;
// Queue
std::queue<CFrame> queue;
};

@ -36,7 +36,7 @@
#include "QnetTypeDefs.h" #include "QnetTypeDefs.h"
#include "QnetConfigure.h" #include "QnetConfigure.h"
#define RELAY_VERSION "QnetRelay-1.0.2" #define RELAY_VERSION "QnetRelay-1.0.3"
std::atomic<bool> CQnetRelay::keep_running(true); std::atomic<bool> CQnetRelay::keep_running(true);
@ -166,7 +166,7 @@ bool CQnetRelay::Run(const char *cfgfile)
len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size); len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size);
if (len < 0) { if (len < 0) {
fprintf(stderr, "ERROR: Run: recvfrom(mmdvm) return error %d: %s\n", errno, strerror(errno)); fprintf(stderr, "ERROR: Run: recvfrom(mmdvmhost) return error %d: %s\n", errno, strerror(errno));
break; break;
} }
@ -248,7 +248,7 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw)
memcpy(dsrp.voice.ambe, dstr.vpkt.vasd.voice, 12); memcpy(dsrp.voice.ambe, dstr.vpkt.vasd.voice, 12);
int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT); int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT);
if (ret != 21) { if (ret != 21) {
printf("ERROR: ProcessGateway: Could not write AMBE mmdvm packet\n"); printf("ERROR: ProcessGateway: Could not write AMBE mmdvmhost packet\n");
return true; return true;
} }
} else { // write a Header packet } else { // write a Header packet
@ -267,7 +267,7 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw)
memcpy(dsrp.header.pfcs, dstr.vpkt.hdr.pfcs, 2); memcpy(dsrp.header.pfcs, dstr.vpkt.hdr.pfcs, 2);
int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT);
if (ret != 49) { if (ret != 49) {
printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); printf("ERROR: ProcessGateway: Could not write Header mmdvmhost packet\n");
return true; return true;
} }
if (log_qso) if (log_qso)
@ -360,13 +360,13 @@ bool CQnetRelay::ReadConfig(const char *cfgFile)
std::string mmdvm_path("module_"); std::string mmdvm_path("module_");
std::string type; std::string type;
if (0 > assigned_module) { if (0 > assigned_module) {
// we need to find the lone mmdvm module // we need to find the lone mmdvmhost module
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
std::string test(mmdvm_path); std::string test(mmdvm_path);
test.append(1, 'a'+i); test.append(1, 'a'+i);
if (cfg.KeyExists(test)) { if (cfg.KeyExists(test)) {
cfg.GetValue(test, estr, type, 1, 16); cfg.GetValue(test, estr, type, 1, 16);
if (type.compare("mmdvm")) if (type.compare("mmdvmhost"))
continue; // this ain't it! continue; // this ain't it!
mmdvm_path.assign(test); mmdvm_path.assign(test);
assigned_module = i; assigned_module = i;
@ -374,16 +374,16 @@ bool CQnetRelay::ReadConfig(const char *cfgFile)
} }
} }
if (0 > assigned_module) { if (0 > assigned_module) {
fprintf(stderr, "Error: no 'mmdvm' module found\n!"); fprintf(stderr, "Error: no 'mmdvmhost' module found\n!");
return true; return true;
} }
} else { } else {
// make sure mmdvm module is defined // make sure mmdvmhost module is defined
mmdvm_path.append(1, 'a' + assigned_module); mmdvm_path.append(1, 'a' + assigned_module);
if (cfg.KeyExists(mmdvm_path)) { if (cfg.KeyExists(mmdvm_path)) {
cfg.GetValue(mmdvm_path, estr, type, 1, 16); cfg.GetValue(mmdvm_path, estr, type, 1, 16);
if (type.compare("mmdvm")) { if (type.compare("mmdvmhost")) {
fprintf(stderr, "%s = %s is not 'mmdvm' type!\n", mmdvm_path.c_str(), type.c_str()); fprintf(stderr, "%s = %s is not 'mmdvmhost' type!\n", mmdvm_path.c_str(), type.c_str());
return true; return true;
} }
} else { } else {

@ -96,29 +96,49 @@ module_x_callsign='' # if you operate in a 'restriction mode', use your pe
module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream 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_acknowledge=false # Do you want an ACK back?
module_x_ack_delay=250 # millisecond delay before acknowledgment module_x_ack_delay=250 # millisecond delay before acknowledgment
module_x_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage
module_x_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak
module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles 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 module_x_agl=0 # the height above ground level for this repeater's antenna
########################################################################################################################## ##########################################################################################################################
# #
# MMDVM - Special parameters when: module_x='mmdvm' # MMDVMHost - Special parameters when: module_x='mmdvmhost'
# #
mmdvm_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program mmdvmhost_tx_frequency=0 # in MHz, not required, set in MMDVM.qn in Hz
mmdvm_gateway_port=20010 # which port will QnetRelay be sending on mmdvmhost_rx_frequency=0 # in Mhz, not required, set in MMDVM.qn in Hz
mmdvm_local_port=20011 # which port will MMDVMHost be sending on 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_ppt_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 - Special parameters when: module_x='itap'
# #
itap_device='/dev/ttyUSB0' # where the serial-to-USB cable show up 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' # 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_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_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 dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case
@ -128,6 +148,8 @@ dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible throug
# DVRPTR - Special parameters when: module_x='dvrptr' # 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 # 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_serial_number='00.00.00.00' # the DVRPTR serial number
dvrptr_rf_on='RFON' # put this in YRCALL to disable the channel 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_rf_off='RFOFF' # put this in YRCALL to enable the channel

@ -99,6 +99,20 @@ InstallSystem () {
fi fi
fi 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 () { BaseStatus () {
@ -149,10 +163,14 @@ ModuleStatus () {
else else
mcvar="n${3}" mcvar="n${3}"
ModuleProcess[$1]="qn$3" ModuleProcess[$1]="qn$3"
if [[ "${ModuleProcess[$1]}" == 'qnmmdvm' ]]; then if [[ "${ModuleProcess[$1]}" == 'qnmmdvmhost' ]]; then
ModuleProcess[$1]='qnrelay' ModuleProcess[$1]='qnrelay'
MMDVMProcess[$1]='mmdvm' MMDVMProcess[$1]='mmdvm'
MMDVMState[$1]='not installed' MMDVMState[$1]='not installed'
elif [[ "${ModuleProcess[$1]}" == 'qnmmdvmmodem' ]] then
ModuleProcess[$1]='qnmodem'
MMDVMProcess[$1]=''
MMDVMState[$1]='EMPTY'
else else
MMDVMState[$1]='EMPTY' MMDVMState[$1]='EMPTY'
MMDVMProcess[$1]='' MMDVMProcess[$1]=''
@ -187,7 +205,7 @@ ModuleStatus () {
MMDVMState[$1]='not installed' MMDVMState[$1]='not installed'
fi fi
fi fi
if [[ "$3" == 'mmdvm' ]]; then if [[ "$3" == 'mmdvmhost' ]]; then
echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]} - ${MMDVMProcess[$1]} is ${MMDVMState[$1]}" echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]} - ${MMDVMProcess[$1]} is ${MMDVMState[$1]}"
else else
echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]}" echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]}"
@ -515,6 +533,7 @@ ndvap=0
ndvrptr=0 ndvrptr=0
nitap=0 nitap=0
nmmdvm=0 nmmdvm=0
nmodem=0
for m in a b c ; do for m in a b c ; do
mod=module_${m} mod=module_${m}
@ -529,13 +548,16 @@ for m in a b c ; do
elif [[ "$type" == 'itap' ]]; then elif [[ "$type" == 'itap' ]]; then
aitap[${nitap}]=${m} aitap[${nitap}]=${m}
nitap=$((nitap + 1)) nitap=$((nitap + 1))
elif [[ "$type" == 'mmdvm' ]]; then elif [[ "$type" == 'mmdvmhost' ]]; then
ammdvm[${nmmdvm}]=${m} ammdvm[${nmmdvm}]=${m}
nmmdvm=$((nmmdvm + 1)) nmmdvm=$((nmmdvm + 1))
elif [[ "$type" == 'mmdvmmodem' ]]; then
amodem[${nmodem}]=${m}
nmodem=$((nmodem + 1))
fi fi
done done
MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm)) MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm + nmodem))
while [[ "$ans" != q* ]]; do while [[ "$ans" != q* ]]; do
clear clear

@ -332,7 +332,8 @@ ModuleMenu () {
echo "1 : DVAP Dongle" echo "1 : DVAP Dongle"
echo "2 : DVRPTR V1" echo "2 : DVRPTR V1"
echo "3 : ICOM Terminal and Access Point Mode" echo "3 : ICOM Terminal and Access Point Mode"
echo "4 : MMDVMHost-based Sytem" echo "4 : MMDVM Modem (like DVMega or ZUMspot) D-Star ONLY!"
echo "5 : MMDVMHost-based Sytem - Requires MMDVMHost"
echo echo
echo " Anything else will return without selecting" echo " Anything else will return without selecting"
echo echo
@ -341,7 +342,8 @@ ModuleMenu () {
if [[ "$key" == 1 ]]; then eval ${mod}=dvap if [[ "$key" == 1 ]]; then eval ${mod}=dvap
elif [[ "$key" == 2 ]]; then eval ${mod}=dvrptr elif [[ "$key" == 2 ]]; then eval ${mod}=dvrptr
elif [[ "$key" == 3 ]]; then eval ${mod}=itap elif [[ "$key" == 3 ]]; then eval ${mod}=itap
elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvm elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvmmodem
elif [[ "$key" == 5 ]]; then eval ${mod}=mmdvmhost
else return else return
fi fi
fi fi
@ -353,21 +355,23 @@ ModuleMenu () {
echo echo
echo -n "ls : Link at startup (must be 8 chars) = "; EvaluateVar {${mod},module_x}_link_at_start echo -n "ls : Link at startup (must be 8 chars) = "; EvaluateVar {${mod},module_x}_link_at_start
echo -n "cs : Callsign (uses ircddb_login if empty) = "; EvaluateVar {${mod},module_x}_callsign echo -n "cs : Callsign (uses ircddb_login if empty) = "; EvaluateVar {${mod},module_x}_callsign
echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},module_x}_frequency
echo -n "of : Offset in Hz = "; EvaluateVar {${mod},module_x}_offset
echo -n "ra : Range (in meters, 1 mile=1609.344 meters) = "; EvaluateVar {${mod},module_x}_range 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 echo -n "ag : Above ground level (in meters) = "; EvaluateVar {${mod},module_x}_agl
if [ -n "$em" ]; then if [ -n "$em" ]; then
echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity
echo -n "wa : Wait this many msec for the next packet = "; EvaluateVar {${mod},module_x}_packet_wait
echo -n "ac : Send acknowledgment on each transmission = "; EvaluateVar {${mod},module_x}_acknowledge 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 "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 fi
if [[ "${!mod}" == dvap ]]; then 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 "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 "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 echo -n "sn : Serial # (visible through the case) = "; EvaluateVar {${mod},dvap}_serial_number
elif [[ "${!mod}" == dvrptr ]]; then 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 "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 "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 "rf : Callsign to turn RF off = "; EvaluateVar {${mod},dvrptr}_rf_off
@ -375,14 +379,32 @@ ModuleMenu () {
echo -n "du : Is duplex = "; EvaluateVar {${mod},dvrptr}_duplex 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 "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 "rq : # of 2 sec interval before system reset = "; EvaluateVar {${mod},dvrptr}_rqst_count
echo -n "ir : Inverse phase of receiver = "; EvaluateVar {${mod},dvrptr}_inverse_rx echo -n "ir : Inverse phase of receiver = "; EvaluateVar {${mod},dvrptr}_rx_invert
echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_inverse_tx echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_tx_invert
elif [[ "${!mod}" == itap ]]; then elif [[ "${!mod}" == 'itap' ]]; then
echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},itap}_frequency
echo -n "dv : USB device path = "; EvaluateVar {${mod},itap}_device echo -n "dv : USB device path = "; EvaluateVar {${mod},itap}_device
elif [[ "${!mod}" == mmdvm ]]; then elif [[ "${!mod}" == 'mmdvmhost' ]]; then
echo -n "ip : Internal IP address = "; EvaluateVar {${mod},mmdvm}_internal_ip echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_tx_frequency
echo -n "gp : Gateway port number = "; EvaluateVar {${mod},mmdvm}_gateway_port echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_rx_frequency
echo -n "lp : Local port number = "; EvaluateVar {${mod},mmdvm}_local_port 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 fi
echo "xx : Delete this module" echo "xx : Delete this module"
EndMenu EndMenu
@ -390,62 +412,75 @@ ModuleMenu () {
if [[ "$key" == ls* ]]; then if [[ "$key" == ls* ]]; then
value="${value:0:8}" value="${value:0:8}"
eval ${mod}_link_at_start="'${value^^}'" 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" == cs* ]]; then eval ${mod}_callsign="${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" == fr* ]]; then eval ${mod}_frequency="$value"
elif [[ "$key" == of* ]]; then eval ${mod}_offset="$value" elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value"
elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value"
elif [[ "$key" == ag* ]]; then eval ${mod}_agl="$value"
elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value" elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value"
elif [[ "$key" == wa* ]]; then eval ${mod}_packet_wait="$value" elif [[ "$key" == ir* ]]; then SetBooleanValue ${mod}_rx_invert "$value"
elif [[ "$key" == ac* ]]; then SetBooleanValue ${mod}_acknowledge "$value" elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value"
elif [[ "$key" == ad* ]]; then eval ${mod}_ack_delay="$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" == po* ]]; then eval ${mod}_power="$value"
elif [[ "$key" == sq* ]]; then eval ${mod}_squelch="$value" elif [[ "$key" == pw* ]]; then eval ${mod}_packet_wait="$value"
elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="${value^^}" elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value"
elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$value"
elif [[ "$key" == rf* ]]; then eval ${mod}_rf_off="$value" elif [[ "$key" == rf* ]]; then eval ${mod}_rf_off="$value"
elif [[ "$key" == rl* ]]; then eval ${mod}_rx_level="$value" elif [[ "$key" == rl* ]]; then eval ${mod}_rx_level="$value"
elif [[ "$key" == du* ]]; then SetBooleanValue ${mod}_duplex "$value" elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$value"
elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$value" elif [[ "$key" == ro* ]]; then eval ${mod}_rx_offset="$value"
elif [[ "$key" == rq* ]]; then eval ${mod}_rqst_count="$value" elif [[ "$key" == rq* ]]; then eval ${mod}_rqst_count="$value"
elif [[ "$key" == ir* ]]; then SetBooleanValue ${mod}_inverse_rx "$value" elif [[ "$key" == rx* ]]; then eval ${mod}_rx_frequency="$value"
elif [[ "$key" == it* ]]; then SetBooleanValue ${mod}_inverse_tx "$value" elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="${value^^}"
elif [[ "$key" == dv* ]]; then eval ${mod}_device="$value" elif [[ "$key" == sq* ]]; then eval ${mod}_squelch="$value"
elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value" elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$value"
elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value" elif [[ "$key" == tl* ]]; then eval ${mod}_tx_level="$value"
elif [[ "$key" == lp* ]]; then eval ${mod}_local_port="$value" elif [[ "$key" == to* ]]; then eval ${mod}_tx_offset="$value"
elif [[ "$key" == tx* ]]; then eval ${mod}_tx_frequency="$value"
elif [[ "$key" == xx* ]]; then elif [[ "$key" == xx* ]]; then
unset ${mod}_{link_at_start,callsign,frequency,offset,range,agl} 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},rx_level} 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,inverse_{t,r}x,device,internal_ip,{gateway,local}_port} unset ${mod}_{duplex,tx_delay,rqst_count,{tx,rx,ptt}_invert,device,internal_ip,{gateway,local}_port}
unset ${mod}_{tx_offset,rx_offset}
unset ${mod} unset ${mod}
return return
elif [[ "$key" == u* ]]; then elif [[ "$key" == u* ]]; then
if [[ "$value" == ls* ]]; then unset ${mod}_link_at_start if [[ "$value" == ac* ]]; then unset ${mod}_acknowledge
elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay
elif [[ "$value" == ag* ]]; then unset ${mod}_agl
elif [[ "$value" == cs* ]]; then unset ${mod}_callsign 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" == fr* ]]; then unset ${mod}_frequency
elif [[ "$value" == of* ]]; then unset ${mod}_offset elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port
elif [[ "$value" == ra* ]]; then unset ${mod}_range
elif [[ "$value" == ag* ]]; then unset ${mod}_agl
elif [[ "$value" == in* ]]; then unset ${mod}_inactivity elif [[ "$value" == in* ]]; then unset ${mod}_inactivity
elif [[ "$value" == wa* ]]; then unset ${mod}_packet_wait elif [[ "$value" == ir* ]]; then unset ${mod}_rx_invert
elif [[ "$value" == ac* ]]; then unset ${mod}_acknowledge elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip
elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay 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" == po* ]]; then unset ${mod}_power
elif [[ "$value" == sq* ]]; then unset ${mod}_squelch elif [[ "$value" == pw* ]]; then unset ${mod}_packet_wait
elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number elif [[ "$value" == ra* ]]; then unset ${mod}_range
elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on
elif [[ "$value" == rf* ]]; then unset ${mod}_rf_off elif [[ "$value" == rf* ]]; then unset ${mod}_rf_off
elif [[ "$value" == rl* ]]; then unset ${mod}_rx_level elif [[ "$value" == rl* ]]; then unset ${mod}_rx_level
elif [[ "$value" == du* ]]; then unset ${mod}_duplex elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on
elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay elif [[ "$value" == ro* ]]; then unset ${mod}_rx_offset
elif [[ "$value" == rq* ]]; then unset ${mod}_rqst_count elif [[ "$value" == rq* ]]; then unset ${mod}_rqst_count
elif [[ "$value" == ir* ]]; then unset ${mod}_inverse_rx elif [[ "$value" == rx* ]]; then unset ${mod}_rx_frequency
elif [[ "$value" == it* ]]; then unset ${mod}_inverse_tx elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number
elif [[ "$value" == dv* ]]; then unset ${mod}_device elif [[ "$value" == sq* ]]; then unset ${mod}_squelch
elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay
elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port elif [[ "$value" == tl* ]]; then unset ${mod}_tx_level
elif [[ "$value" == lp* ]]; then unset ${mod}_local_port elif [[ "$value" == to* ]]; then unset ${mod}_tx_offset
elif [[ "$value" == tx* ]]; then unset ${mod}_tx_frequency
fi fi
fi fi
done done
@ -474,7 +509,6 @@ WriteCFGFile () {
echo "${p}=${!p}" >> $outFile echo "${p}=${!p}" >> $outFile
q=${p}_link_at_start; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile q=${p}_link_at_start; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_callsign; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile q=${p}_callsign; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_offset; [ -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}_range; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
@ -483,10 +517,13 @@ WriteCFGFile () {
q=${p}_acknowledge; [ -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 q=${p}_ack_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
if [[ "${!p}" == "dvap" ]]; then 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}_power; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_squelch; [ -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 q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
elif [[ "${!p}" == "dvrptr" ]]; then 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}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_rf_on; [ -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}_rf_off; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
@ -494,14 +531,29 @@ WriteCFGFile () {
q=${p}_duplex; [ -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}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_rqst_count; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_rqst_count; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_inverse_rx; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_tx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_inverse_tx; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_rx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
elif [[ "${!p}" == "itap" ]]; then elif [[ "${!p}" == "itap" ]]; then
q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile
elif [[ "${!p}" == "mmdvm" ]]; then 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}_internal_ip; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile
q=${p}_gateway_port; [ -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 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
fi fi
fi fi
done done

@ -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
Loading…
Cancel
Save

Powered by TurnKey Linux.