diff --git a/ACKNOWLEDGEMENTS b/ACKNOWLEDGEMENTS index bbe8892..d57b186 100644 --- a/ACKNOWLEDGEMENTS +++ b/ACKNOWLEDGEMENTS @@ -1,14 +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! -Some parts of are also inspired from ircDDBGateway by Jonathan Naylor, G4KLX and his copyright appears in those files -that used his ideas. The TCPReaderWriterClient is copied in whole. Thanks Jonathan! +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. +QnetGateway continues to be published under Version 2 of the GNU General Public +License, see the LICENSE file. Tom n7tae (at) arrl (dot) net diff --git a/Makefile b/Makefile index a7fd66e..66a9bac 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) 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 all : $(ALL_PROGRAMS) @@ -46,6 +46,7 @@ relay : qnrelay dvap : qndvap dvrptr : qndvrptr itap : qnitap +modem : qnmodem qngateway : QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS) 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 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) g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread @@ -127,6 +131,14 @@ installitap : qnitap systemctl daemon-reload 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 ######### QnetDVAP ######### /bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE) @@ -188,6 +200,14 @@ uninstallmmdvm : /bin/rm -f $(CFGDIR)/MMDVM$(MODULE).qn sudo systemctl daemon-reload +uninstallmodem : + ######### QnetModem ######### + systemctl stop qnmodem$(MODULE).service + systemctl disable qnmodem$(MODULE).service + /bin/rm -f $(SYSDIR)/qnmodem$(MODULE).service + /bin/rm -f $(BINDIR)/qnmodem$(MODULE) + systemctl daemon-reload + uninstallitap : ######### QnetITAP ######### systemctl stop qnitap$(MODULE).service diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index aa2693c..7a0c41d 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -27,7 +27,7 @@ #include "UnixDgramSocket.h" #include "QnetConfigure.h" -#define DVRPTR_VERSION "QnetDVRPTR-6.0.2" +#define DVRPTR_VERSION "QnetDVRPTR-6.0.3" #define BAUD B115200 #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+"_inverse_rx", type, RX_Inverse); - cfg.GetValue(path+"_inverse_tx", type, TX_Inverse); + cfg.GetValue(path+"_rx_invert", type, RX_Inverse); + cfg.GetValue(path+"_tx_invert", type, TX_Inverse); path.assign("timing_"); cfg.GetValue(path+"timeout_remote_g2", estr, REMOTE_TIMEOUT, 1, 10); diff --git a/QnetGateway.cpp b/QnetGateway.cpp index f1d8d66..a9ef094 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -57,7 +57,7 @@ #include "QnetConfigure.h" #include "QnetGateway.h" -#define IRCDDB_VERSION "QnetGateway-8.1.0" +#define IRCDDB_VERSION "QnetGateway-8.1.1" extern void dstar_dv_init(); 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; } else { printf("Found Module: %s = '%s'\n", path.c_str(), type.c_str()); - if (0 == type.compare("dvap")) { - rptr.mod[m].package_version = "QnetDVAP"; - } else if (0 == type.compare("dvrptr")) { - rptr.mod[m].package_version = "QnetDVRPTR"; - } else if (0 == type.compare("mmdvm")) { - rptr.mod[m].package_version = "QnetRelay"; - } else if (0 == type.compare("itap")) { - rptr.mod[m].package_version = "QnetITAP"; + if (0 == type.compare("dvap")) { rptr.mod[m].package_version = "QnetDVAP"; + } else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version = "QnetDVRPTR"; + } else if (0 == type.compare("mmdvmhost")) { rptr.mod[m].package_version = "QnetRelay"; + } else if (0 == type.compare("mmdvmmodem")) { rptr.mod[m].package_version = "QnetModem"; + } else if (0 == type.compare("itap")) { rptr.mod[m].package_version = "QnetITAP"; } else { printf("module type '%s' is invalid\n", type.c_str()); return true; @@ -251,8 +248,19 @@ bool CQnetGateway::read_config(char *cfgFile) rptr.mod[m].defined = true; path.append(1, '_'); - cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0e12); - cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); + if (cfg.KeyExists(path+"tx_frequency")) { + 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+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); diff --git a/QnetModem.cpp b/QnetModem.cpp new file mode 100644 index 0000000..c8b2487 --- /dev/null +++ b/QnetModem.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QnetModem.h" +#include "QnetTypeDefs.h" +#include "QnetConfigure.h" + +#define MODEM_VERSION "QnetModem-0.0.0" +#define MAX_RESPONSES 30 + +std::atomic 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 +#include +#include +#include + +#include +#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 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 queue; +}; diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 9b0cee7..3ab56fe 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -36,7 +36,7 @@ #include "QnetTypeDefs.h" #include "QnetConfigure.h" -#define RELAY_VERSION "QnetRelay-1.0.2" +#define RELAY_VERSION "QnetRelay-1.0.3" std::atomic CQnetRelay::keep_running(true); @@ -166,7 +166,7 @@ bool CQnetRelay::Run(const char *cfgfile) len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size); 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; } @@ -248,7 +248,7 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) memcpy(dsrp.voice.ambe, dstr.vpkt.vasd.voice, 12); int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT); if (ret != 21) { - printf("ERROR: ProcessGateway: Could not write AMBE mmdvm packet\n"); + printf("ERROR: ProcessGateway: Could not write AMBE mmdvmhost packet\n"); return true; } } else { // write a Header packet @@ -267,7 +267,7 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) memcpy(dsrp.header.pfcs, dstr.vpkt.hdr.pfcs, 2); int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); if (ret != 49) { - printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); + printf("ERROR: ProcessGateway: Could not write Header mmdvmhost packet\n"); return true; } if (log_qso) @@ -360,13 +360,13 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) std::string mmdvm_path("module_"); std::string type; 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++) { std::string test(mmdvm_path); test.append(1, 'a'+i); if (cfg.KeyExists(test)) { cfg.GetValue(test, estr, type, 1, 16); - if (type.compare("mmdvm")) + if (type.compare("mmdvmhost")) continue; // this ain't it! mmdvm_path.assign(test); assigned_module = i; @@ -374,16 +374,16 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) } } if (0 > assigned_module) { - fprintf(stderr, "Error: no 'mmdvm' module found\n!"); + fprintf(stderr, "Error: no 'mmdvmhost' module found\n!"); return true; } } else { - // make sure mmdvm module is defined + // make sure mmdvmhost module is defined mmdvm_path.append(1, 'a' + assigned_module); if (cfg.KeyExists(mmdvm_path)) { cfg.GetValue(mmdvm_path, estr, type, 1, 16); - if (type.compare("mmdvm")) { - fprintf(stderr, "%s = %s is not 'mmdvm' type!\n", mmdvm_path.c_str(), type.c_str()); + if (type.compare("mmdvmhost")) { + fprintf(stderr, "%s = %s is not 'mmdvmhost' type!\n", mmdvm_path.c_str(), type.c_str()); return true; } } else { diff --git a/defaults b/defaults index 64061e9..8b6bfdb 100644 --- a/defaults +++ b/defaults @@ -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_acknowledge=false # Do you want an ACK back? 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_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 -mmdvm_gateway_port=20010 # which port will QnetRelay be sending on -mmdvm_local_port=20011 # which port will MMDVMHost be sending on +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_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_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 @@ -128,6 +148,8 @@ dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible throug # 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 diff --git a/qnadmin b/qnadmin index b11b763..680d4a7 100755 --- a/qnadmin +++ b/qnadmin @@ -99,6 +99,20 @@ InstallSystem () { 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 () { @@ -149,10 +163,14 @@ ModuleStatus () { else mcvar="n${3}" ModuleProcess[$1]="qn$3" - if [[ "${ModuleProcess[$1]}" == 'qnmmdvm' ]]; then + 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]='' @@ -187,7 +205,7 @@ ModuleStatus () { MMDVMState[$1]='not installed' fi fi - if [[ "$3" == 'mmdvm' ]]; then + 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]}" @@ -515,6 +533,7 @@ ndvap=0 ndvrptr=0 nitap=0 nmmdvm=0 +nmodem=0 for m in a b c ; do mod=module_${m} @@ -529,13 +548,16 @@ for m in a b c ; do elif [[ "$type" == 'itap' ]]; then aitap[${nitap}]=${m} nitap=$((nitap + 1)) - elif [[ "$type" == 'mmdvm' ]]; then + 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)) +MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm + nmodem)) while [[ "$ans" != q* ]]; do clear diff --git a/qnconfig b/qnconfig index 6a5ee71..9efc838 100755 --- a/qnconfig +++ b/qnconfig @@ -332,7 +332,8 @@ ModuleMenu () { echo "1 : DVAP Dongle" echo "2 : DVRPTR V1" 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 " Anything else will return without selecting" echo @@ -341,7 +342,8 @@ ModuleMenu () { 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}=mmdvm + elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvmmodem + elif [[ "$key" == 5 ]]; then eval ${mod}=mmdvmhost else return fi fi @@ -353,21 +355,23 @@ ModuleMenu () { echo 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 "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 "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 "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 "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 + 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 + 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 @@ -375,14 +379,32 @@ ModuleMenu () { 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}_inverse_rx - echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_inverse_tx - elif [[ "${!mod}" == itap ]]; then + 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}" == mmdvm ]]; then - echo -n "ip : Internal IP address = "; EvaluateVar {${mod},mmdvm}_internal_ip - echo -n "gp : Gateway port number = "; EvaluateVar {${mod},mmdvm}_gateway_port - echo -n "lp : Local port number = "; EvaluateVar {${mod},mmdvm}_local_port + 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 @@ -390,62 +412,75 @@ ModuleMenu () { 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" == 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" == of* ]]; then eval ${mod}_offset="$value" - elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value" - elif [[ "$key" == ag* ]]; then eval ${mod}_agl="$value" + elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value" elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value" - elif [[ "$key" == wa* ]]; then eval ${mod}_packet_wait="$value" - elif [[ "$key" == ac* ]]; then SetBooleanValue ${mod}_acknowledge "$value" - elif [[ "$key" == ad* ]]; then eval ${mod}_ack_delay="$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" == sq* ]]; then eval ${mod}_squelch="$value" - elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="${value^^}" - elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$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" == du* ]]; then SetBooleanValue ${mod}_duplex "$value" - elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$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" == ir* ]]; then SetBooleanValue ${mod}_inverse_rx "$value" - elif [[ "$key" == it* ]]; then SetBooleanValue ${mod}_inverse_tx "$value" - elif [[ "$key" == dv* ]]; then eval ${mod}_device="$value" - elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value" - elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value" - elif [[ "$key" == lp* ]]; then eval ${mod}_local_port="$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,frequency,offset,range,agl} - unset ${mod}_{inactivity,packet_wait,acknowledge,ack_delay,power,squelch,serial_number,rf_o{n,ff},rx_level} - unset ${mod}_{duplex,tx_delay,rqst_count,inverse_{t,r}x,device,internal_ip,{gateway,local}_port} + 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} unset ${mod} return 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" == du* ]]; then unset ${mod}_duplex + elif [[ "$value" == dv* ]]; then unset ${mod}_device elif [[ "$value" == fr* ]]; then unset ${mod}_frequency - elif [[ "$value" == of* ]]; then unset ${mod}_offset - elif [[ "$value" == ra* ]]; then unset ${mod}_range - elif [[ "$value" == ag* ]]; then unset ${mod}_agl + elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port elif [[ "$value" == in* ]]; then unset ${mod}_inactivity - elif [[ "$value" == wa* ]]; then unset ${mod}_packet_wait - elif [[ "$value" == ac* ]]; then unset ${mod}_acknowledge - elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay + 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" == sq* ]]; then unset ${mod}_squelch - elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number - elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on + 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" == du* ]]; then unset ${mod}_duplex - elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay + 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" == ir* ]]; then unset ${mod}_inverse_rx - elif [[ "$value" == it* ]]; then unset ${mod}_inverse_tx - elif [[ "$value" == dv* ]]; then unset ${mod}_device - elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip - elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port - elif [[ "$value" == lp* ]]; then unset ${mod}_local_port + 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 @@ -474,7 +509,6 @@ WriteCFGFile () { echo "${p}=${!p}" >> $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}_frequency; [ -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 @@ -483,10 +517,13 @@ WriteCFGFile () { 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 @@ -494,14 +531,29 @@ WriteCFGFile () { 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}_inverse_rx; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile - q=${p}_inverse_tx; [ -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}" == "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}_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 fi fi done diff --git a/system/qnmodem.service b/system/qnmodem.service new file mode 100644 index 0000000..2b86700 --- /dev/null +++ b/system/qnmodem.service @@ -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