├── .gitignore ├── ACKNOWLEDGEMENTS ├── Base.h ├── CONFIG+INSTALL ├── COPYING ├── CacheManager.cpp ├── CacheManager.h ├── DASHBOARD.README ├── DPlusAuthenticator.cpp ├── DPlusAuthenticator.h ├── DStarDecode.cpp ├── DStarDecode.h ├── DTMF+REMOTE+VOICE.README ├── DVAPDongle.cpp ├── DVAPDongle.h ├── HostQueue.h ├── LICENSE ├── Location.cpp ├── Location.h ├── MMDVM.README ├── Makefile ├── OPERATING ├── QnetConfigure.cpp ├── QnetConfigure.h ├── QnetDB.cpp ├── QnetDB.h ├── QnetDVAP.cpp ├── QnetDVAP.h ├── QnetDVRPTR.cpp ├── QnetDVRPTR.h ├── QnetGateway.cpp ├── QnetGateway.h ├── QnetITAP.cpp ├── QnetITAP.h ├── QnetLink.cpp ├── QnetLink.h ├── QnetModem.cpp ├── QnetModem.h ├── QnetRelay.cpp ├── QnetRelay.h ├── QnetRemote.cpp ├── QnetTypeDefs.h ├── QnetVoice.cpp ├── README.md ├── Random.h ├── SEcho.h ├── SockAddress.h ├── TCPReaderWriterClient.cpp ├── TCPReaderWriterClient.h ├── Timer.h ├── UDPSocket.cpp ├── UDPSocket.h ├── UnixDgramSocket.cpp ├── UnixDgramSocket.h ├── Utilities.h ├── announce ├── already_linked.dat ├── already_unlinked.dat ├── baddtmfcmd.dat ├── connected2network.dat ├── failed_link.dat ├── gatewaynotfound.dat ├── gatewayrestart.dat ├── id.dat ├── index.dat ├── linked.dat ├── notincache.dat ├── rebooting.dat ├── shutdown.dat ├── speak.dat └── unlinked.dat ├── aprs.cpp ├── aprs.h ├── bash_aliases ├── dashboardV2 ├── bin │ ├── getJson.php │ └── qnRemoteCmd.php ├── css │ ├── bootstrap-table.min.css │ ├── bootstrap.min.css │ └── bootstrap.min.css.map ├── index.php ├── init.php ├── js │ ├── bootstrap-table-auto-refresh.min.js │ ├── bootstrap-table.min.js │ ├── bootstrap.min.js │ ├── bootstrap.min.js.map │ └── jquery.min.js └── jsonData │ └── README ├── defaults ├── exec_G.sh ├── exec_H.sh ├── exec_R.sh ├── index.php ├── ircddb ├── IRCClient.cpp ├── IRCClient.h ├── IRCDDB.cpp ├── IRCDDB.h ├── IRCDDBApp.cpp ├── IRCDDBApp.h ├── IRCMessage.cpp ├── IRCMessage.h ├── IRCMessageQueue.cpp ├── IRCMessageQueue.h ├── IRCProtocol.cpp ├── IRCProtocol.h ├── IRCReceiver.cpp ├── IRCReceiver.h ├── IRCutils.cpp └── IRCutils.h ├── qn.dvap.cfg ├── qn.everything.cfg ├── qn.itap.cfg ├── qn.mmdvm.cfg ├── qnadmin ├── qnconfig ├── qndtmf.sh └── system ├── gateway.timer ├── mmdvm.service ├── mmdvm.timer ├── qndash.service.80 ├── qndtmf.service ├── qndvap.service ├── qndvrptr.service ├── qngateway.service ├── qnitap.service ├── qnlink.service ├── qnmodem.service └── qnrelay.service /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *~ 4 | *.gch 5 | .vscode 6 | qn.cfg 7 | qndtmf 8 | gwys.txt 9 | qnitap 10 | qndvap 11 | qndv 12 | qndvrptr 13 | qnlink 14 | qngateway 15 | qnremote 16 | qnvoice 17 | qnrelay 18 | qnmodem 19 | system/qndash.service 20 | My_Hosts.txt 21 | dashboardV2/jsonData/*.json 22 | -------------------------------------------------------------------------------- /ACKNOWLEDGEMENTS: -------------------------------------------------------------------------------- 1 | QnetGateway started as g2_ircddb written by Scott Lawson, KI4LKF. 2 | 3 | QnetGateway uses IRCDDB software written by Michael Dirska, DL1BFF. Both Scott and 4 | Michael published their code under version 2 of the GNU General Public License. The 5 | current form of QnetGateway would be completely impossible without Scott and 6 | Michael's contribution. Thank you for a great starting point of this current 7 | project! 8 | 9 | Some parts of some QnetGateway programs are also inspired by ircDDBGateway, 10 | DStarRepeater and MMDVMHost by Jonathan Naylor G4KLX, and his copyright appears in 11 | those files that used his ideas and in some cases, use his source code. 12 | 13 | QnetGateway continues to be published under Version 2 of the GNU General Public 14 | License, see the LICENSE file. 15 | 16 | Tom 17 | n7tae (at) arrl (dot) net 18 | -------------------------------------------------------------------------------- /Base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2020 by Thomas Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | class CBase 25 | { 26 | public: 27 | CBase() { keep_running = true; } 28 | virtual ~CBase() {} 29 | virtual bool Initialize(const std::string &path) = 0; 30 | virtual void Run() = 0; 31 | virtual void Close() = 0; 32 | void Stop() { keep_running = false; } 33 | protected: 34 | std::atomic keep_running; 35 | void AddFDSet(int &max, int newfd, fd_set *set) 36 | { 37 | if (newfd > max) 38 | max = newfd; 39 | FD_SET(newfd, set); 40 | } 41 | }; 42 | 43 | class CModem : public CBase 44 | { 45 | public: 46 | CModem(int index = -1) : CBase(), m_index(index) {} 47 | virtual ~CModem() {} 48 | 49 | protected: 50 | int m_index; 51 | }; 52 | -------------------------------------------------------------------------------- /CacheManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "CacheManager.h" 20 | 21 | void CCacheManager::findUserData(const std::string &user, std::string &rptr, std::string &gate, std::string &addr) 22 | { 23 | mux.lock(); 24 | rptr.assign(findUserRptr(user)); 25 | gate.assign(findRptrGate(rptr)); 26 | addr.assign(findGateAddr(gate)); 27 | mux.unlock(); 28 | } 29 | 30 | void CCacheManager::findRptrData(const std::string &rptr, std::string &gate, std::string &addr) 31 | { 32 | mux.lock(); 33 | gate.assign(findRptrGate(rptr)); 34 | addr.assign(findGateAddr(gate)); 35 | mux.unlock(); 36 | } 37 | 38 | std::string CCacheManager::findUserAddr(const std::string &user) 39 | { 40 | mux.lock(); 41 | std::string addr(findGateAddr(findRptrGate(findUserRptr(user)))); 42 | mux.unlock(); 43 | 44 | return addr; 45 | } 46 | 47 | std::string CCacheManager::findUserTime(const std::string &user) 48 | { 49 | std::string utime; 50 | if (user.empty()) 51 | return utime; 52 | mux.lock(); 53 | auto itt = UserTime.find(user); 54 | if (itt != UserTime.end()) 55 | utime.assign(itt->second); 56 | mux.unlock(); 57 | return utime; 58 | } 59 | 60 | std::string CCacheManager::findUserRepeater(const std::string &user) 61 | { 62 | mux.lock(); 63 | std::string rptr(findUserRptr(user)); 64 | mux.unlock(); 65 | return rptr; 66 | } 67 | 68 | std::string CCacheManager::findGateAddress(const std::string &gate) 69 | { 70 | mux.lock(); 71 | std::string addr(findGateAddr(gate)); 72 | mux.unlock(); 73 | return addr; 74 | } 75 | 76 | std::string CCacheManager::findNameNick(const std::string &name) 77 | { 78 | std::string nick; 79 | if (name.empty()) 80 | return nick; 81 | mux.lock(); 82 | auto itn = NameNick.find(name); 83 | if (itn != NameNick.end()) 84 | nick.assign(itn->second); 85 | mux.unlock(); 86 | return nick; 87 | } 88 | 89 | std::string CCacheManager::findServerUser() 90 | { 91 | std::string suser; 92 | mux.lock(); 93 | for (auto it=NameNick.begin(); it!=NameNick.end(); it++) 94 | { 95 | if (0 == it->first.compare(0, 2, "s-")) 96 | { 97 | suser.assign(it->first); 98 | break; 99 | } 100 | } 101 | mux.unlock(); 102 | return suser; 103 | } 104 | 105 | void CCacheManager::updateUser(const std::string &user, const std::string &rptr, const std::string &gate, const std::string &addr, const std::string &time) 106 | { 107 | if (user.empty()) 108 | return; 109 | 110 | mux.lock(); 111 | if (! time.empty()) 112 | UserTime[user] = time; 113 | 114 | if (rptr.empty()) 115 | { 116 | mux.unlock(); 117 | return; 118 | } 119 | 120 | UserRptr[user] = rptr; 121 | 122 | if (gate.empty() || addr.empty()) 123 | { 124 | mux.unlock(); 125 | return; 126 | } 127 | 128 | if (rptr.compare(0, 7, gate, 0, 7)) 129 | RptrGate[rptr] = gate; // only do this if they differ 130 | 131 | GateAddr[gate] = addr; 132 | mux.unlock(); 133 | } 134 | 135 | void CCacheManager::updateRptr(const std::string &rptr, const std::string &gate, const std::string &addr) 136 | { 137 | if (rptr.empty() || gate.empty()) 138 | return; 139 | 140 | mux.lock(); 141 | RptrGate[rptr] = gate; 142 | if (addr.empty()) 143 | { 144 | mux.unlock(); 145 | return; 146 | } 147 | GateAddr[gate] = addr; 148 | mux.unlock(); 149 | } 150 | 151 | void CCacheManager::updateGate(const std::string &G, const std::string &addr) 152 | { 153 | if (G.empty() || addr.empty()) 154 | return; 155 | std::string gate(G); 156 | auto p = gate.find('_'); 157 | while (gate.npos != p) 158 | { 159 | gate[p] = ' '; 160 | p = gate.find('_'); 161 | } 162 | mux.lock(); 163 | GateAddr[gate] = addr; 164 | mux.unlock(); 165 | } 166 | 167 | void CCacheManager::updateName(const std::string &name, const std::string &nick) 168 | { 169 | if (name.empty() || nick.empty()) 170 | return; 171 | mux.lock(); 172 | NameNick[name] = nick; 173 | mux.unlock(); 174 | } 175 | 176 | void CCacheManager::eraseGate(const std::string &gate) 177 | { 178 | mux.lock(); 179 | GateAddr.erase(gate); 180 | mux.unlock(); 181 | } 182 | 183 | void CCacheManager::eraseName(const std::string &name) 184 | { 185 | mux.lock(); 186 | NameNick.erase(name); 187 | mux.unlock(); 188 | } 189 | 190 | void CCacheManager::clearGate() 191 | { 192 | mux.lock(); 193 | GateAddr.clear(); 194 | NameNick.clear(); 195 | mux.unlock(); 196 | } 197 | 198 | // these last three functions are private and not mux locked. 199 | std::string CCacheManager::findUserRptr(const std::string &user) 200 | { 201 | std::string rptr; 202 | if (user.empty()) 203 | return rptr; 204 | auto it = UserRptr.find(user); 205 | if (it != UserRptr.end()) 206 | rptr.assign(it->second); 207 | return rptr; 208 | } 209 | 210 | std::string CCacheManager::findRptrGate(const std::string &rptr) 211 | { 212 | std::string gate; 213 | if (rptr.empty()) 214 | return gate; 215 | auto it = RptrGate.find(rptr); 216 | if (it == RptrGate.end()) 217 | { 218 | gate.assign(rptr); 219 | gate[7] = 'G'; 220 | } 221 | else 222 | gate.assign(it->second); 223 | return gate; 224 | } 225 | 226 | std::string CCacheManager::findGateAddr(const std::string &gate) 227 | { 228 | std::string addr; 229 | if (gate.empty()) 230 | return addr; 231 | auto ita = GateAddr.find(gate); 232 | if (ita != GateAddr.end()) 233 | addr.assign(ita->second); 234 | return addr; 235 | } 236 | -------------------------------------------------------------------------------- /CacheManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | class CCacheManager 26 | { 27 | public: 28 | CCacheManager() {} 29 | ~CCacheManager() {} 30 | 31 | // the bodies of these public functions are mux locked to access the maps and the private functions. 32 | // for these find functions, if a map value can't be found the returned string will be empty. 33 | void findUserData(const std::string &user, std::string &rptr, std::string &gate, std::string &addr); 34 | void findRptrData(const std::string &rptr, std::string &gate, std::string &addr); 35 | std::string findUserTime(const std::string &user); 36 | std::string findUserAddr(const std::string &user); 37 | std::string findNameNick(const std::string &name); 38 | std::string findUserRepeater(const std::string &user); 39 | std::string findGateAddress(const std::string &gate); 40 | std::string findServerUser(); 41 | void eraseGate(const std::string &gate); 42 | void eraseName(const std::string &name); 43 | void clearGate(); 44 | 45 | void updateUser(const std::string &user, const std::string &rptr, const std::string &gate, const std::string &addr, const std::string &time); 46 | void updateRptr(const std::string &rptr, const std::string &gate, const std::string &addr); 47 | void updateGate(const std::string &gate, const std::string &addr); 48 | void updateName(const std::string &name, const std::string &nick); 49 | 50 | private: 51 | // these three functions aren't mux locked, that's why they're private 52 | std::string findUserRptr(const std::string &user); 53 | std::string findRptrGate(const std::string &rptr); 54 | std::string findGateAddr(const std::string &gate); 55 | 56 | std::unordered_map UserTime; 57 | std::unordered_map UserRptr; 58 | std::unordered_map RptrGate; 59 | std::unordered_map GateAddr; 60 | std::unordered_map NameNick; 61 | std::mutex mux; 62 | }; 63 | -------------------------------------------------------------------------------- /DASHBOARD.README: -------------------------------------------------------------------------------- 1 | ##### DASHBOARD.README ##### 2 | 3 | -------------------------------------------------------------------------- 4 | IMPORTANT! 5 | This document describes how to install a dashboard server based on a php 6 | mini-server and should not be used to publish a QnetGateway dashboard on 7 | the world-wide-web. This mini-server for operating behind a firewall on 8 | a local network. If you need your QnetGateway dashboard on the WWW, install 9 | a real web server, like apache2. The mini-server described here is not 10 | suitable for the WWW! 11 | -------------------------------------------------------------------------- 12 | 13 | A web-based dashboard can be enabled. The dashboard has several sections 14 | that you can display in any order. You can also control the refresh rate and 15 | how many rows you want in the Last Heard section. You can configure these 16 | features with the ./qnconfig menu. Don't set the refresh time faster than about 17 | 10 seconds. If you do, it will become difficult to use the Send URCall button. 18 | 19 | By default, the installed web server will use ip/port 0.0.0.0:80. If you want 20 | to use something else, the copy the qndash service file: 21 | 22 | cp system/qndash.service.80 system/qndash.service 23 | 24 | and edit your new file *before* installing the dashboard. 25 | 26 | You also need several php libraries: 27 | 28 | sudo apt install -y php-common php-fpm sqlite3 php-sqlite3 dnsutils 29 | 30 | To install the dashboard system, run the following command: 31 | 32 | sudo make installdash 33 | 34 | Note that this will install a php web server and all necessary packages needed 35 | for the server. 36 | 37 | To uninstall, run the following: 38 | 39 | sudo make uninstalldash 40 | 41 | These installed "necessary packages" are not uninstalled by the uninstall script. 42 | It will only shut down the php web server. 43 | 44 | Once the dashboard server is running, simply point a browser at the Hotspot's IP 45 | address or at http://.local/ (on the same subnet). 46 | 47 | Please note that this is a very simple server and is not recommended for the 48 | world wide web. If you want a robust dashboard accessible from the web, you 49 | will want to use a hardened server, like apache, and a different index.php file. 50 | 51 | Please note that if you are typing in a URCALL when the webpage is refreshing, 52 | the entry field will loose focus and you'll have to try again. 53 | 54 | #### DASHBOARD V2 #### 55 | 56 | A new responsive dashboard built on the Bootstrap 4.5 framework has been 57 | added. If you have configured the dashboard with the commands above you can 58 | access the new dashboard by appending "/dashboardV2" to the URL. For example 59 | http://.local/dashboardV2 60 | 61 | Notes: 62 | The responsive dashboard currently has the following features 63 | * Page does not have to reload to refresh its data solving the URCall focus issue 64 | * Dashboard refresh is suggested to be set at 10 seconds. Any value less than 8 will 65 | have no effect. 66 | * Mobile design friendly. On smaller screens some columns in the tables will be 67 | hidden, however, simply rotate your mobile device to landscape view and they 68 | should appear 69 | * QnRemote section (aka URCall section) has improved design for mobile device use 70 | -------------------------------------------------------------------------------- /DPlusAuthenticator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 by Jonathan Naylor G4KLX 3 | * Copyright (C) 2018-2020 by Thomas A. Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "DPlusAuthenticator.h" 32 | #include "Utilities.h" 33 | 34 | CDPlusAuthenticator::CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address) : 35 | m_loginCallsign(loginCallsign), 36 | m_address(address) 37 | { 38 | assert(loginCallsign.size()); 39 | 40 | trim(m_loginCallsign); 41 | } 42 | 43 | CDPlusAuthenticator::~CDPlusAuthenticator() 44 | { 45 | } 46 | 47 | int CDPlusAuthenticator::Process(CQnetDB &db, const bool reflectors, const bool repeaters) 48 | // return true if everything went okay 49 | { 50 | int result = client.Open(m_address, AF_UNSPEC, "20001"); 51 | if (result) 52 | { 53 | fprintf(stderr, "DPlus Authorization failed: %s\n", gai_strerror(result)); 54 | return 0; 55 | } 56 | return authenticate(db, reflectors, repeaters); 57 | } 58 | 59 | int CDPlusAuthenticator::authenticate(CQnetDB &db, const bool reflectors, const bool repeaters) 60 | { 61 | unsigned char buffer[4096U]; 62 | ::memset(buffer, ' ', 56U); 63 | 64 | buffer[0U] = 0x38U; 65 | buffer[1U] = 0xC0U; 66 | buffer[2U] = 0x01U; 67 | buffer[3U] = 0x00U; 68 | 69 | ::memcpy(buffer+4, m_loginCallsign.c_str(), m_loginCallsign.size()); 70 | ::memcpy(buffer+12, "DV019999", 8); 71 | ::memcpy(buffer+28, "W7IB2", 5); 72 | ::memcpy(buffer+40, "DHS0257", 7); 73 | 74 | if (client.Write(buffer, 56U)) 75 | { 76 | fprintf(stderr, "ERROR: could not write opening phrase\n"); 77 | client.Close(); 78 | return 0; 79 | } 80 | 81 | int ret = client.ReadExact(buffer, 2U); 82 | unsigned int rval = 0; 83 | CHostQueue hqueue; 84 | 85 | while (ret == 2) 86 | { 87 | unsigned int len = (buffer[1U] & 0x0FU) * 256U + buffer[0U]; 88 | // Ensure that we get exactly len - 2U bytes from the TCP stream 89 | ret = client.ReadExact(buffer + 2U, len - 2U); 90 | if (0 > ret) 91 | { 92 | fprintf(stderr, "Problem reading line, it returned %d\n", errno); 93 | return rval; 94 | } 95 | 96 | if ((buffer[1U] & 0xC0U) != 0xC0U || buffer[2U] != 0x01U) 97 | { 98 | fprintf(stderr, "Invalid packet received from 20001\n"); 99 | return rval; 100 | } 101 | 102 | for (unsigned int i = 8U; (i + 25U) < len; i += 26U) 103 | { 104 | std::string address((char *)(buffer + i)); 105 | std::string name((char *)(buffer + i + 16U)); 106 | 107 | trim(address); 108 | trim(name); 109 | name.resize(6, ' '); 110 | 111 | // Get the active flag 112 | bool active = (buffer[i + 25U] & 0x80U) == 0x80U; 113 | 114 | // An empty name or IP address or an inactive gateway/reflector is not added 115 | if (address.size()>0U && name.size()>0U && active) 116 | { 117 | if (reflectors && 0==name.compare(0, 3, "REF")) 118 | { 119 | rval++; 120 | hqueue.Push(CHost(name.c_str(), address.c_str(), 20001)); 121 | } 122 | else if (repeaters && name.compare(0, 3, "REF")) 123 | { 124 | rval++; 125 | hqueue.Push(CHost(name.c_str(), address.c_str(), 20001)); 126 | } 127 | } 128 | } 129 | if (! hqueue.Empty()) 130 | db.UpdateGW(hqueue); 131 | 132 | ret = client.ReadExact(buffer, 2U); 133 | } 134 | 135 | printf("Probably authorized DPlus on %s using callsign %s\n", m_address.c_str(), m_loginCallsign.c_str()); 136 | client.Close(); 137 | 138 | return rval; 139 | } 140 | -------------------------------------------------------------------------------- /DPlusAuthenticator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX 4 | * Copyright (C) 2018-2020 by Thomas A. Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | **/ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "TCPReaderWriterClient.h" 26 | #include "QnetDB.h" 27 | 28 | class CDPlusAuthenticator 29 | { 30 | public: 31 | CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address); 32 | ~CDPlusAuthenticator(); 33 | 34 | int Process(CQnetDB &qn, const bool reflectors, const bool repeaters); 35 | 36 | private: 37 | std::string m_loginCallsign; 38 | std::string m_address; 39 | CTCPReaderWriterClient client; 40 | 41 | int authenticate(CQnetDB &db, const bool reflectors, const bool repeaters); 42 | }; 43 | -------------------------------------------------------------------------------- /DStarDecode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1994 by Robert Morelos-Zaragoza. All rights reserved. 3 | * See http://www.eccpage.com/golay23.c 4 | * Copyright (C) 2010 by Michael Dirska, DL1BFF (dl1bff@mdx.de) 5 | * Copyright (C) 2020 by Thomas Early N7TAE 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 | */ 21 | 22 | #include "DStarDecode.h" 23 | 24 | #define X22 0x00400000 /* vector representation of X^{22} */ 25 | #define X11 0x00000800 /* vector representation of X^{11} */ 26 | #define MASK12 0xfffff800 /* auxiliary vector for testing */ 27 | #define GENPOL 0x00000c75 /* generator polinomial, g(x) */ 28 | 29 | static const int bit_pos1[] = 30 | { 31 | 0, 0, 1, 1, 2, 2, 32 | 0, 0, 1, 1, 2, 2, 33 | 0, 0, 1, 1, 2, 2, 34 | 0, 0, 1, 1, 2, 2, 35 | 0, 0, 1, 1, 2, 2, 36 | 0, 0, 1, 1, 2, 2, 37 | 38 | 0, 0, 1, 1, 2, 2, 39 | 0, 0, 1, 1, 2, 2, 40 | 0, 0, 1, 1, 2, 2, 41 | 0, 0, 1, 1, 2, 2, 42 | 0, 0, 1, 1, 2, 2, 43 | 0, 0, 1, 1, 2, 2 44 | }; 45 | 46 | static const int bit_pos2[] = 47 | { 48 | 23, 11, 23, 11, 23, 11, 49 | 22, 10, 22, 10, 22, 10, 50 | 21, 9, 21, 9, 21, 9, 51 | 20, 8, 20, 8, 20, 8, 52 | 19, 7, 19, 7, 19, 7, 53 | 18, 6, 18, 6, 18, 6, 54 | 17, 5, 17, 5, 17, 5, 55 | 16, 4, 16, 4, 16, 4, 56 | 15, 3, 15, 3, 15, 3, 57 | 14, 2, 14, 2, 14, 2, 58 | 13, 1, 13, 1, 13, 1, 59 | 12, 0, 12, 0, 12, 0 60 | }; 61 | 62 | CDStarDecode::CDStarDecode(void) 63 | { 64 | long temp; 65 | int i; 66 | int a[4]; 67 | 68 | decoding_table[0] = 0; 69 | decoding_table[1] = 1; 70 | temp = 1; 71 | for (i=2; i<= 23; i++) 72 | { 73 | temp = temp << 1; 74 | decoding_table[get_syndrome(temp)] = temp; 75 | } 76 | 77 | a[1] = 1; 78 | a[2] = 2; 79 | temp = arr2int(a,2); 80 | decoding_table[get_syndrome(temp)] = temp; 81 | for (i=1; i<253; i++) 82 | { 83 | nextcomb(23,2,a); 84 | temp = arr2int(a,2); 85 | decoding_table[get_syndrome(temp)] = temp; 86 | } 87 | 88 | a[1] = 1; 89 | a[2] = 2; 90 | a[3] = 3; 91 | temp = arr2int(a,3); 92 | decoding_table[get_syndrome(temp)] = temp; 93 | for (i=1; i<1771; i++) 94 | { 95 | nextcomb(23,3,a); 96 | temp = arr2int(a,3); 97 | decoding_table[get_syndrome(temp)] = temp; 98 | } 99 | 100 | for (i=0; i < 4096; i++) 101 | { 102 | int mask = 0x800000; 103 | int j; 104 | int pr; 105 | 106 | prng[i] = 0; 107 | pr = i << 4; 108 | 109 | for (j=0; j < 24; j++) 110 | { 111 | pr = ((173 * pr) + 13849) & 0xFFFF; 112 | 113 | if ((pr & 0x8000) != 0) 114 | { 115 | prng[i] |= mask; 116 | } 117 | 118 | mask = mask >> 1; 119 | } 120 | } 121 | } 122 | 123 | long CDStarDecode::arr2int(int *a, int r) 124 | /* 125 | * Convert a binary vector of Hamming weight r, and nonzero positions in 126 | * array a[1]...a[r], to a long integer \sum_{i=1}^r 2^{a[i]-1}. 127 | */ 128 | { 129 | int i; 130 | long mul, result = 0, temp; 131 | 132 | for (i=1; i<=r; i++) 133 | { 134 | mul = 1; 135 | temp = a[i]-1; 136 | while (temp--) 137 | mul = mul << 1; 138 | result += mul; 139 | } 140 | return(result); 141 | } 142 | 143 | void CDStarDecode::nextcomb(int n, int r, int *a) 144 | /* 145 | * Calculate next r-combination of an n-set. 146 | */ 147 | { 148 | int i, j; 149 | 150 | a[r]++; 151 | if (a[r] <= n) 152 | return; 153 | j = r - 1; 154 | while (a[j] == n - r + j) 155 | j--; 156 | for (i = r; i >= j; i--) 157 | a[i] = a[j] + i - j + 1; 158 | return; 159 | } 160 | 161 | long CDStarDecode::get_syndrome(long pattern) 162 | /* 163 | * Compute the syndrome corresponding to the given pattern, i.e., the 164 | * remainder after dividing the pattern (when considering it as the vector 165 | * representation of a polynomial) by the generator polynomial, GENPOL. 166 | * In the program this pattern has several meanings: (1) pattern = infomation 167 | * bits, when constructing the encoding table; (2) pattern = error pattern, 168 | * when constructing the decoding table; and (3) pattern = received vector, to 169 | * obtain its syndrome in decoding. 170 | */ 171 | { 172 | // long aux = X22, aux2; 173 | long aux = X22; 174 | 175 | if (pattern >= X11) 176 | while (pattern & MASK12) 177 | { 178 | while (!(aux & pattern)) 179 | aux = aux >> 1; 180 | pattern ^= (aux/X11) * GENPOL; 181 | } 182 | return(pattern); 183 | } 184 | 185 | int CDStarDecode::golay2412(int data, int *decoded) 186 | { 187 | int block = (data >> 1) & 0x07fffff; 188 | int corrected_block = block ^ decoding_table[get_syndrome(block)]; 189 | 190 | int errs = 0; 191 | int parity_corr = 0; 192 | int i; 193 | 194 | for (i = 0; i < 23; i++) 195 | { 196 | int mask = 1 << i; 197 | 198 | int bit_rcvd = block & mask; 199 | int bit_corr = corrected_block & mask; 200 | 201 | if (bit_corr != 0) 202 | { 203 | parity_corr ++; 204 | } 205 | 206 | if (bit_rcvd != bit_corr) 207 | { 208 | errs ++; 209 | } 210 | } 211 | 212 | if ((parity_corr & 0x01) != (data & 0x01)) 213 | { 214 | errs ++; 215 | } 216 | 217 | *decoded = corrected_block >> 11; 218 | 219 | return errs; 220 | } 221 | 222 | int CDStarDecode::Decode(const unsigned char *d, int data[3]) 223 | { 224 | int bits[3]; 225 | int i; 226 | int errs; 227 | 228 | for (i=0; i < 3; i++) 229 | { 230 | bits[i] = 0; 231 | } 232 | 233 | for (i=0; i < 72; i++) 234 | { 235 | bits[ bit_pos1[i] ] |= (d[ i >> 3 ] & (0x80 >> (i & 0x07))) ? (1 << bit_pos2[i]) : 0; 236 | } 237 | 238 | errs = golay2412( bits[0], data ); 239 | 240 | errs += golay2412( bits[1] ^ prng[ data[0] & 0x0fff ], data + 1 ); 241 | 242 | data[2] = bits[2]; 243 | 244 | return errs; 245 | } 246 | -------------------------------------------------------------------------------- /DStarDecode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright (c) 1994 by Robert Morelos-Zaragoza. All rights reserved. 4 | * See http://www.eccpage.com/golay23.c 5 | * Copyright (C) 2010 by Michael Dirska, DL1BFF (dl1bff@mdx.de) 6 | * Copyright (C) 2020 by Thomas Early N7TAE 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | */ 22 | 23 | class CDStarDecode 24 | { 25 | public: 26 | CDStarDecode(); 27 | ~CDStarDecode() {} 28 | int Decode(const unsigned char *d, int data[3]); 29 | 30 | private: 31 | // functions 32 | long get_syndrome(long pattern); 33 | long arr2int(int a[], int r); 34 | void nextcomb(int n, int r, int a[]); 35 | int golay2412(int data, int *decoded); 36 | 37 | // data 38 | long decoding_table[2048]; 39 | int prng[4096]; 40 | }; 41 | -------------------------------------------------------------------------------- /DTMF+REMOTE+VOICE.README: -------------------------------------------------------------------------------- 1 | ####### DTMF ######## 2 | 3 | DTMF is available with the QnetGateway Software. You can do things like linking 4 | and unlinking from you radio's keyboard, if present. For example, typing 5 | "B75703" should link you to XRF757 C. 6 | 7 | DTMF is not enabled by default. You can install and uninstall it from the 8 | qnadmin script. 9 | 10 | Be sure to look at the 'dtmf' script. It contains examples of all the DTMF 11 | commands it supports. You can add more if you are good at shell programming 12 | and understand how qnremote works. 13 | 14 | ######## QnetRemote ######### 15 | 16 | QnetRemote is a command line program used to send any arbitrary YourCall to your 17 | QnetGateway system. It is install automatically when you install any of the 18 | supported modems: MMDVMHost, QnetDVAP or QnetDVRPTR. It's a very simple, yet 19 | powerful program. Open a shell to you system and type "qnremote" and it will 20 | remind you of the format it expects: 21 | 22 | pi@raspberrypi:~ $ qnremote 23 | Usage: qnremote 24 | Example: qnremote c n7tae xrf757cl 25 | Where... 26 | c is the local repeater module 27 | n7tae is the value of mycall 28 | xrf757cl is the value of yourcall, in this case this is a Link command 29 | 30 | You simple specify the module the command will be sent to, and the MyCall and 31 | YourCall parameters. Here are some more examples: 32 | 33 | qnremote b w4wwm u # W4WWM is unlinking module B. 34 | qnremote c w1bwb i # W1BSB is requesting the status of module C 35 | 36 | Modules, callsigns and YourCall can all be in lowercase, qnremote will conver 37 | them to uppercase. QnetLink will validate that the specific MyCall is allowed 38 | to link or unlink, according to the configuration. (By default, any user can 39 | link or unlink a module, unless link_unlink is specified in the configuration 40 | file, see qn.everything.cfg.) 41 | 42 | qnremote can be used by the linux cron facility to automatically execute jobs 43 | at a certain time. If you want to link to XRF002 A on Saturday at 6:00 PM 44 | Mountain Time for the D-Star Users Net, don't forget to include an unlink 45 | command in your cron-executed script before you link! For instructions on how 46 | to do this search the web with "linux cron job". 47 | 48 | ######## QnetVoice ######## 49 | 50 | QnetVoice is another command line program to send any ambe-formatted file 51 | to a module configured on you gateway. It has a simple interface, like 52 | QnetRemote. To get started, open a shell on your system and type "qnvoice": 53 | 54 | pi@raspberrypi:~ $ qnvoice 55 | Usage: qnvoice 56 | Where... 57 | module is one of your modules 58 | mycall is your personal callsign 59 | dat_file is a .dat voice file file 60 | 61 | Please note that and are not case sensitive, but 62 | is. 63 | 64 | So what's a .dat voice file? All the voice prompts delivered with QnetGateway, 65 | in the QnetGateway/announce directory are .dat files. And, you can 66 | easily create your own! 67 | 68 | To make your own .dat voice file file, just put " S0" in your radio's 69 | YourCall and key up and talk. You will be making a "voice mail" .dat file: 70 | /tmp/X_voicemail.dat2, where X is the module on which you are transmitting. 71 | Once you have created a voicemail file, you can move it out of the /tmp 72 | directory and rename it, but if you want to use it in a qnvoice command, 73 | you need to strip off the first 57 bytes. You can do this with the command: 74 | 75 | tail -c +57 /tmp/X_voicemail.dat2 > mynewvoiceprompt.dat 76 | 77 | Then you can use it in a qnvoice command. You can also replace all of the 78 | standard voice messages with your own. If you want to do this put your 79 | versions in a directory outside of the build directory and make a small 80 | script that will copy your messages over the existing, installed 81 | messages: 82 | 83 | #!/bin/bash 84 | 85 | sudo cp ./id.dat /usr/local/etc 86 | ... more copy commands 87 | 88 | That way, when you pull down a new release and build and install it, you 89 | can quickly update the voice messages with your own. 90 | -------------------------------------------------------------------------------- /DVAPDongle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2017,2020,2021 by Thomas Early, N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | 22 | enum REPLY_TYPE 23 | { 24 | RT_TIMEOUT, 25 | RT_ERR, 26 | RT_UNKNOWN, 27 | RT_NAME, 28 | RT_SER, 29 | RT_FW, 30 | RT_START, 31 | RT_STOP, 32 | RT_MODU, 33 | RT_MODE, 34 | RT_SQL, 35 | RT_PWR, 36 | RT_OFF, 37 | RT_FREQ, 38 | RT_FREQ_LIMIT, 39 | RT_STS, 40 | RT_PTT, 41 | RT_ACK, 42 | RT_HDR, 43 | RT_HDR_ACK, 44 | RT_DAT 45 | }; 46 | 47 | #pragma pack(push,1) 48 | using SDVAP_REGISTER = struct dvp_register_tag 49 | { 50 | uint16_t header; 51 | union 52 | { 53 | uint8_t nul; 54 | struct 55 | { 56 | uint16_t control; 57 | union 58 | { 59 | int8_t byte; 60 | int16_t word; 61 | int32_t dword; 62 | int32_t twod[2]; 63 | char sstr[12]; 64 | uint8_t ustr[12]; 65 | }; 66 | } param; 67 | struct 68 | { 69 | uint16_t streamid; 70 | uint8_t framepos; 71 | uint8_t seq; 72 | union 73 | { 74 | struct 75 | { 76 | unsigned char flag[3]; 77 | unsigned char rpt2[8]; 78 | unsigned char rpt1[8]; 79 | unsigned char urcall[8]; 80 | unsigned char mycall[8]; 81 | unsigned char sfx[4]; 82 | unsigned char pfcs[2]; 83 | } hdr; 84 | struct 85 | { 86 | unsigned char voice; 87 | unsigned char sdata; 88 | } vad; 89 | }; 90 | } frame; 91 | }; 92 | }; 93 | #pragma pack(pop) 94 | 95 | class CDVAPDongle 96 | { 97 | public: 98 | CDVAPDongle(); 99 | ~CDVAPDongle(); 100 | bool Initialize(const char *devpath, const char *serialno, const int frequency, const int offset, const int power, const int squelch); 101 | REPLY_TYPE GetReply(SDVAP_REGISTER &dr); 102 | void Stop(); 103 | int KeepAlive(); 104 | void SendRegister(SDVAP_REGISTER &dr); 105 | 106 | private: 107 | // data 108 | int serfd; 109 | const unsigned int MAX_REPL_CNT; 110 | uint32_t frequency; 111 | int32_t offset; 112 | SDVAP_REGISTER dvapreg; 113 | 114 | // functions 115 | bool open_serial(const char *device); 116 | bool open_device(const char *device); 117 | int read_from_dvp(void* buf, unsigned int len); 118 | int write_to_dvp(const void* buf, const unsigned int len); 119 | bool syncit(); 120 | bool get_ser(const char *dvp, const char *dvap_serial_number); 121 | bool get_name(); 122 | bool get_fw(); 123 | bool set_modu(); 124 | bool set_mode(); 125 | bool set_sql(int squelch); 126 | bool set_pwr(int power); 127 | bool set_off(int offset); 128 | bool set_freq(int frequency); 129 | bool start_dvap(); 130 | }; 131 | -------------------------------------------------------------------------------- /HostQueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | class CHost 25 | { 26 | public: 27 | CHost() {} 28 | 29 | ~CHost() {} 30 | 31 | CHost(const CHost &from) 32 | { 33 | name.assign(from.name); 34 | addr.assign(from.addr); 35 | port = from.port; 36 | } 37 | 38 | CHost(const std::string n, const std::string a, unsigned short p) 39 | { 40 | name.assign(n); 41 | addr.assign(a); 42 | port = p; 43 | } 44 | 45 | CHost &operator=(const CHost &from) 46 | { 47 | name.assign(from.name); 48 | addr.assign(from.addr); 49 | port = from.port; 50 | return *this; 51 | } 52 | 53 | std::string name, addr; 54 | unsigned short port; 55 | }; 56 | 57 | template class CTQueue 58 | { 59 | public: 60 | CTQueue() {} 61 | 62 | ~CTQueue() 63 | { 64 | Clear(); 65 | } 66 | 67 | void Push(T item) 68 | { 69 | queue.push(item); 70 | } 71 | 72 | T Pop() 73 | { 74 | T item = queue.front(); 75 | queue.pop(); 76 | return item; 77 | } 78 | 79 | bool Empty() 80 | { 81 | return queue.empty(); 82 | } 83 | 84 | void Clear() 85 | { 86 | while (queue.size()) 87 | queue.pop(); 88 | } 89 | 90 | private: 91 | std::queue queue; 92 | }; 93 | 94 | using CHostQueue = CTQueue; 95 | -------------------------------------------------------------------------------- /Location.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 by Thomas Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "Utilities.h" 25 | #include "Location.h" 26 | 27 | CLocation::CLocation() 28 | { 29 | gps = std::regex("[^0-9]([0-9]{1,2})([0-9]{2}\\.[0-9]{1,}),?([NS])[/,]([0-9]{1,3})([0-9]{2}\\.[0-9]{1,}),?([WE])", std::regex::extended); 30 | } 31 | 32 | // returns true on success 33 | bool CLocation::Parse(const char *instr) 34 | { 35 | std::string s(instr); 36 | std::cmatch cm; 37 | trim(s); 38 | if (s.size() < 20) 39 | return false; 40 | 41 | if (! std::regex_search(s.c_str(), cm, gps, std::regex_constants::match_default)) 42 | { 43 | //std::cerr << "Unsuccessful gps parse of '" << s << "'" << std::endl; 44 | return false; 45 | } 46 | 47 | auto size = cm.size(); 48 | if (size != 7) 49 | { 50 | std::cerr << "Bad CRC Match for " << s << ":"; 51 | for (unsigned i=0; iAPDPRS,DSTAR*,qAR,%s:!%02d%04.2f%c/%03d%04.2f%c/A\r\n", call.c_str(), station, int(lat), latmin, (latitude>=0) ? 'N' : 'S', int(lon), lonmin, (longitude>=0) ? 'E' : 'W'); 123 | else 124 | snprintf(aprs, 128, "%s-%c>APDPRS,DSTAR*,qAR,%s:!%02d%04.2f%c/%03d%04.2f%c/A\r\n", call.c_str(), last, station, int(lat), latmin, (latitude>=0) ? 'N' : 'S', int(lon), lonmin, (longitude>=0) ? 'E' : 'W'); 125 | 126 | return aprs; 127 | } 128 | 129 | double CLocation::Latitude() const 130 | { 131 | return latitude; 132 | } 133 | 134 | double CLocation::Longitude() const 135 | { 136 | return longitude; 137 | } 138 | 139 | const char* CLocation::MaidenHead() const 140 | { 141 | return maidenhead; 142 | } 143 | -------------------------------------------------------------------------------- /Location.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2020 by Thomas Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | 23 | class CLocation 24 | { 25 | public: 26 | CLocation(); 27 | bool Parse(const char *instr); 28 | double Latitude() const; 29 | double Longitude() const; 30 | const char* MaidenHead() const; 31 | const char* APRS(std::string &call, const char *station); 32 | private: 33 | char maidenhead[7]; 34 | char aprs[128]; 35 | double latitude, longitude; 36 | std::regex gps; 37 | }; 38 | -------------------------------------------------------------------------------- /MMDVM.README: -------------------------------------------------------------------------------- 1 | 2 | Building a QnetGateway + MMDVMHost System 3 | 4 | Copyright (C) 2018 by Thomas A. Early N7TAE 5 | 6 | I'll assume you'll be doing this on a Raspberry Pi, but any modern Debian-based 7 | system should work. It just needs a g++ compiler with version greater than 4.9. 8 | These instructions assume you have configured your system with the locale, keyboard 9 | and time zone. When choosing locale, always choose a "UTF-8" version of your 10 | locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" before 11 | your start. On a Raspberry Pi, you can do all of this with the configuration menu: 12 | "sudo raspi-config". 13 | 14 | If you are using a device that uses the GPIO header on the raspberry pi, you need to 15 | disable the serial0 console in the /boot/cmdline.txt file: Remove the reference to 16 | "console=serial0,115200" in this file. You should also disable bluetooth by adding: 17 | "dtoverlay=pi3-disable-bt" (without the quotes) to the end of the /boot/config.txt. 18 | 19 | In your parent directory of your QnetGateway build... 20 | 21 | 1) Clone the QnetGateway repository: git clone https://github.com/n7tae/QnetGateway.git 22 | Clone the MMDVMHost repository: git clone https://github.com/g4klx/MMDVMHost.git 23 | Likewise if you want DMRGateway: git clone https://github.com/g4klx/DMRGateway.git 24 | Likewise if you want YSFGateway: git clone https://github.com/g4klx/YSFClients.git 25 | 26 | 2) cd to the QnetGateway directory and configure it: cd QnetGateway; ./qnconfig 27 | Make sure you set your callsign in the irc menu and set an mmdvmhost modem 28 | in module A, B or C. Write out the configuration file before you leave qnconfig. 29 | 30 | 3) cd into the MMDVMHost directory and copy the ini file template: 31 | cp MMDVM.ini MMDVM.qn 32 | Edit the MMDVM.qn file. Set your Callsign and Id. Turn off duplex. Enable an 33 | external display, if you have one. Set the Frequency(s). Set the Latitude and 34 | other location info if you want. Change the log levels. I use DisplayLevel=2 35 | and FileLevel=0. Set the Port on your modem. Disable all services you aren't 36 | going to use. Very important: Set the [D-Star] Module. For UHF use B and for 37 | VHF use C. 38 | 39 | 4) If you are using the DMRGateway cd to that directory and copy the ini file: 40 | cp DMRGateway.ini DMRGateway.qn 41 | Edit this new DMRGateway.qn file for your system. 42 | 43 | 5) If you are using the YSFGateway cd to that directory and copy the ini file: 44 | cp YSFGateway.ini YSFGateway.qn 45 | In the [General] section, set your callsign and suffix (either RPT or ND). 46 | In the [Info] section, set your RX & TX frequencies, and any other details 47 | as you like. In the [Log] section, set your log levels as your like, set a 48 | /FULL/PATH/TO/LOGROOTFILE for the log, and define the FileRoot--or disable 49 | logging by setting both DisplayLevel and FileLevel to '0'. in the [Network] 50 | section, set a startup reflector (FCS, YSF or XLX), and set InactivityTimeout 51 | (in minutes) as desired. If the reflector is changed, it will return to the 52 | startup reflector after the inactivity timeout is reached if 'Revert' is set 53 | to '1'. 'Options' allows you to monitor multiple DG-IDs if connected to a YCS 54 | server such as QuadNet's FCS310. You set the TX DG-ID on your radio to which 55 | ever DG-ID you wish to transmit into. This has its benfits and drawbacks. 56 | The to TX DG-ID is a global setting on Yaesu radios, so you cannot save it in 57 | a channel like a DMR talkgroup. You must change the DG-ID in the GM menu each 58 | time. Basically, it is good for monitoring, a little cumbersome for talking. 59 | For [YSFNetwork], set the /FULL/PATH/TO/YSFHosts.txt. And finally for 60 | [FCSNetwork], set the /FULL/PATH/TO/FCSRooms.txt. __NEVER__ connect to a 61 | transcoding reflector network such as QuadNet with more than one mode on the 62 | same MMDVM hotspot/ node/ repeater. 63 | 64 | 6) Next, your ready to install your software. For that, first move back to the 65 | QnetGateway directory andstart the administration script: ./qnadmin 66 | First you want to create you gwys.txt file. This file contains a list of 67 | reflectors and repeaters you might like to link. Got into the gwys.txt menu 68 | creation sub-menu with "gw" and select one of three different methods to 69 | generate a gwys.txt file. You can add and/or delete records manually in 70 | this sub-menu. Return to the main menu because your now ready to build and 71 | install your system. Install your configured system with "is". This will 72 | compile and install everything that you have configured. 73 | 74 | 7) There are many additional things that can be done in the administration menu. 75 | Enter the log menu with "l" and from there you can view logs from each 76 | running process. 77 | 78 | 8) DTMF is _not_ enabled by default if you want it, type "id" in the main menu. 79 | -------------------------------------------------------------------------------- /QnetConfigure.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include "QnetConfigure.h" 22 | 23 | CQnetConfigure::CQnetConfigure() 24 | { 25 | } 26 | 27 | CQnetConfigure::~CQnetConfigure() 28 | { 29 | defaults.clear(); 30 | cfg.clear(); 31 | } 32 | 33 | char *CQnetConfigure::Trim(char *s) 34 | { 35 | size_t len = strlen(s); 36 | while (len && isspace(s[len-1])) 37 | s[--len] = '\0'; 38 | while (*s && len && isspace(*s)) 39 | len = strlen(++s); 40 | return s; 41 | } 42 | 43 | bool CQnetConfigure::ReadConfigFile(const std::string &configfile, std::map &amap) 44 | { 45 | FILE *fp = fopen(configfile.c_str(), "r"); 46 | if (fp) 47 | { 48 | char line[2048]; 49 | while (fgets(line, 2048, fp)) 50 | { 51 | char *key = strtok(line, "="); 52 | key = Trim(key); 53 | if (strlen(key) && '#' != *key) 54 | { 55 | char *val = strtok(NULL, "\r\n"); 56 | char *val2 = Trim(val); 57 | if ('\'' == val2[0]) 58 | { 59 | if ('\'' == val2[1]) 60 | val[0] = '\0'; 61 | else 62 | val = strtok(val2, "'"); 63 | } 64 | else 65 | val = strtok(val2, "# \t"); 66 | amap[key] = val; 67 | } 68 | } 69 | fclose(fp); 70 | return false; 71 | } 72 | fprintf(stderr, "could not open file %s\n", configfile.c_str()); 73 | return true; 74 | } 75 | 76 | bool CQnetConfigure::Initialize(const std::string &file) 77 | { 78 | std::string filename(CFG_DIR); 79 | filename.append("/defaults"); 80 | if (ReadConfigFile(filename.c_str(), defaults)) 81 | return true; 82 | return ReadConfigFile(file, cfg); 83 | } 84 | 85 | bool CQnetConfigure::KeyExists(const std::string &key) 86 | { 87 | return (cfg.end() != cfg.find(key)); 88 | } 89 | 90 | bool CQnetConfigure::GetDefaultBool(const std::string &path, const std::string &mod, bool &dvalue) 91 | { 92 | std::string value; 93 | if (GetDefaultString(path, mod, value)) 94 | return true; // No default value defined! 95 | if ('0'==value.at(0) || 'f'==value.at(0) || 'F'==value.at(0)) 96 | dvalue = false; 97 | else if ('1'==value.at(0) || 't'==value.at(0) || 'T'==value.at(0)) 98 | dvalue = true; 99 | else 100 | { 101 | fprintf(stderr, "%s=%s doesn't seem to be a boolean!\n", path.c_str(), value.c_str()); 102 | return true; 103 | } 104 | return false; 105 | } 106 | 107 | bool CQnetConfigure::GetDefaultDouble(const std::string &path, const std::string &mod, double &dvalue) 108 | { 109 | std::string value; 110 | if (GetDefaultString(path, mod, value)) 111 | return true; // No default value defined! 112 | dvalue = std::stod(value); 113 | return false; 114 | } 115 | 116 | bool CQnetConfigure::GetDefaultInt(const std::string &path, const std::string &mod, int &dvalue) 117 | { 118 | std::string value; 119 | if (GetDefaultString(path, mod, value)) 120 | return true; // No default value defined! 121 | dvalue = std::stoi(value); 122 | return false; 123 | } 124 | 125 | bool CQnetConfigure::GetDefaultString(const std::string &path, const std::string &mod, std::string &dvalue) 126 | { 127 | std::string search, search_again; 128 | if (mod.empty()) 129 | { 130 | search = path + "_d"; // there is no mod, so this is a simple search 131 | } 132 | else 133 | { 134 | search_again = mod; // we're looking from a module value. We may have to look for non-generic module parameters 135 | if (0==path.compare(0, 7, "module_") && ('a'==path.at(7) || 'b'==path.at(7) || 'c'==path.at(7)) && '_'==path.at(8)) 136 | { 137 | // path begins with module_{a|b|c}_ 138 | if (0==mod.compare("dvrptr") || 0==mod.compare("dvap") || 0==mod.compare("mmdvmhost") || 0==mod.compare("mmdvmmodem") || 0==mod.compare("itap") || 0==mod.compare("thumbdv")) 139 | { 140 | // and the module is recognized 141 | search = path; 142 | search.replace(7, 1, 1, 'x'); 143 | search_again += path.substr(8); // now the search_again path might look like dvap_frequency, for example. 144 | } 145 | else 146 | { 147 | fprintf(stderr, "Unrecognized module type = '%s'\n", mod.c_str()); 148 | return true; 149 | } 150 | } 151 | else 152 | { 153 | fprintf(stderr, "%s looks like an ilformed request from module '%s'\n", path.c_str(), mod.c_str()); 154 | return true; 155 | } 156 | } 157 | auto it = defaults.find(search); 158 | if (defaults.end() == it) 159 | { 160 | it = defaults.find(search_again); 161 | if (defaults.end() == it) 162 | return true; 163 | } 164 | dvalue = it->second; 165 | return false; 166 | } 167 | 168 | bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, bool &value) 169 | { 170 | auto it = cfg.find(path); 171 | if (cfg.end() == it) 172 | { 173 | bool dvalue; 174 | if (GetDefaultBool(path, mod, dvalue)) 175 | { 176 | fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); 177 | return true; 178 | } 179 | value = dvalue; // found a value in the defaults 180 | } 181 | else // found a value in the cfg file 182 | { 183 | char c = it->second.at(0); 184 | if ('0'==c || 'f'==c || 'F'==c) 185 | value = false; 186 | else if ('1'==c || 't'==c || 'T'==c) 187 | value = true; 188 | else 189 | { 190 | fprintf(stderr, "%s=%s doesn't seem to define a boolean\n", path.c_str(), it->second.c_str()); 191 | return true; 192 | } 193 | } 194 | printf("%s = %s\n", path.c_str(), value ? "true" : "false"); 195 | return false; 196 | } 197 | 198 | bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max) 199 | { 200 | auto it = cfg.find(path); 201 | if (cfg.end() == it) 202 | { 203 | double dvalue; 204 | if (GetDefaultDouble(path, mod, dvalue)) 205 | { 206 | fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); 207 | return true; 208 | } 209 | if (dvalue < min || dvalue > max) 210 | { 211 | fprintf(stderr, "Default value %s=%g is out of acceptable range\n", path.c_str(), value); 212 | return true; 213 | } 214 | value = dvalue; 215 | } 216 | else 217 | { 218 | value = std::stod(it->second); 219 | if (value < min || value > max) 220 | { 221 | fprintf(stderr, "%s=%g is out of acceptable range\n", path.c_str(), value); 222 | return true; 223 | } 224 | } 225 | printf("%s = %g\n", path.c_str(), value); 226 | return false; 227 | } 228 | 229 | bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max) 230 | { 231 | auto it = cfg.find(path); 232 | if (cfg.end() == it) 233 | { 234 | int dvalue; 235 | if (GetDefaultInt(path, mod, dvalue)) 236 | { 237 | fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str()); 238 | return true; 239 | } 240 | if (dvalue < min || dvalue > max) 241 | { 242 | fprintf(stderr, "Default value %s=%d is out of acceptable range\n", path.c_str(), value); 243 | return true; 244 | } 245 | value = dvalue; 246 | } 247 | else 248 | { 249 | value = std::stoi(it->second); 250 | if (value < min || value > max) 251 | { 252 | fprintf(stderr, "%s=%s is out of acceptable range\n", path.c_str(), it->second.c_str()); 253 | return true; 254 | } 255 | } 256 | printf("%s = %d\n", path.c_str(), value); 257 | return false; 258 | } 259 | 260 | bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, std::string &value, int min, int max) 261 | { 262 | auto it = cfg.find(path); 263 | if (cfg.end() == it) 264 | { 265 | std::string dvalue; 266 | if (GetDefaultString(path, mod, dvalue)) 267 | { 268 | fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str()); 269 | return true; 270 | } 271 | int l = dvalue.length(); 272 | if (min-1>=l || l>max) 273 | { 274 | printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str()); 275 | return true; 276 | } 277 | value.assign(dvalue); 278 | } 279 | else 280 | { 281 | value.assign(it->second); 282 | int l = value.length(); 283 | if (lmax) 284 | { 285 | printf("%s='%s' is wrong size\n", path.c_str(), value.c_str()); 286 | return true; 287 | } 288 | } 289 | printf("%s = '%s'\n", path.c_str(), value.c_str()); 290 | return false; 291 | } 292 | -------------------------------------------------------------------------------- /QnetConfigure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | class CQnetConfigure 25 | { 26 | public: 27 | CQnetConfigure(); 28 | virtual ~CQnetConfigure(); 29 | bool Initialize(const std::string &path); 30 | bool GetValue(const std::string &path, const std::string &mod, bool &value); 31 | bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); 32 | bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max); 33 | bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max); 34 | bool KeyExists(const std::string &key); 35 | 36 | private: 37 | std::map defaults; 38 | std::map cfg; 39 | 40 | char *Trim(char *s); 41 | bool ReadConfigFile(const std::string &file, std::map &amap); 42 | bool GetDefaultBool (const std::string &key, const std::string &mod, bool &dval); 43 | bool GetDefaultDouble(const std::string &key, const std::string &mod, double &dval); 44 | bool GetDefaultInt (const std::string &key, const std::string &mod, int &dval); 45 | bool GetDefaultString(const std::string &key, const std::string &mod, std::string &dval); 46 | }; 47 | -------------------------------------------------------------------------------- /QnetDB.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright (C) 2020 by Thomas Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "HostQueue.h" 26 | 27 | class CLink 28 | { 29 | public: 30 | CLink(const std::string &call, const unsigned char *addr, time_t ltime) : callsign(call), address((const char *)addr), linked_time(ltime) {} 31 | 32 | CLink(const CLink &from) 33 | { 34 | callsign.assign(from.callsign); 35 | address.assign(from.address), 36 | linked_time=from.linked_time; 37 | } 38 | 39 | CLink &operator=(const CLink &from) 40 | { 41 | callsign.assign(from.callsign); 42 | address.assign(from.address), 43 | linked_time=from.linked_time; 44 | return *this; 45 | } 46 | 47 | ~CLink() {} 48 | 49 | std::string callsign, address; 50 | time_t linked_time; 51 | }; 52 | 53 | class CQnetDB 54 | { 55 | public: 56 | CQnetDB() : db(NULL) {} 57 | ~CQnetDB() { if (db) sqlite3_close(db); } 58 | bool Open(const char *name); 59 | bool UpdateLH(const char *callsign, const char *sfx, const char module, const char *reflector); 60 | bool UpdateMessage(const char *callsign, const char *message); 61 | bool UpdatePosition(const char *callsign, const char *maidenhead, double latitude, double longitude); 62 | bool UpdateLS(const char *address, const char from_mod, const char *to_callsign, const char to_mod, time_t connect_time); 63 | bool UpdateGW(CHostQueue &); 64 | bool DeleteLS(const char *address); 65 | bool FindLS(const char mod, std::list &linklist); 66 | bool FindGW(const char *name, std::string &address, unsigned short &port); 67 | bool FindGW(const char *name); 68 | void ClearLH(); 69 | void ClearLS(); 70 | void ClearGW(); 71 | int Count(const char *table); 72 | 73 | private: 74 | bool Init(); 75 | bool UpdateGW(const char *name, const char *address, unsigned short port); 76 | sqlite3 *db; 77 | }; 78 | -------------------------------------------------------------------------------- /QnetDVAP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2018,2020 by Thomas A. Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | 23 | #include "UnixDgramSocket.h" 24 | #include "Base.h" 25 | 26 | using SDVAP_ACK_ARG = struct davp_ack_arg_tag 27 | { 28 | char mycall[8]; 29 | float ber; 30 | }; 31 | 32 | class CQnetDVAP : public CModem 33 | { 34 | public: 35 | CQnetDVAP(int index) : CModem(index) {} 36 | ~CQnetDVAP() {} 37 | bool Initialize(const std::string &path); 38 | void Run(); 39 | void Close(); 40 | 41 | private: 42 | bool ReadConfig(const std::string &path); 43 | void ReadFromGateway(); 44 | void calcPFCS(unsigned char *packet, unsigned char *pfcs); 45 | void ReadDVAPThread(); 46 | void RptrAckThread(SDVAP_ACK_ARG *parg); 47 | 48 | 49 | // classes 50 | CDStarDecode decode; 51 | CDVAPDongle dongle; 52 | CRandom Random; 53 | 54 | // data 55 | std::future m_readThread; 56 | 57 | // unix sockets 58 | CUnixDgramWriter ToGate; 59 | CUnixDgramReader FromGate; 60 | /* Default configuration data */ 61 | std::string RPTR; 62 | std::string OWNER; 63 | char RPTR_MOD; 64 | std::string MODULE_SERIAL_NUMBER; /* AP123456 */ 65 | std::string MODULE_DEVICE; /* /dev/ttyUSBx */ 66 | int MODULE_FREQUENCY; /* between 144000000 and 148000000 */ 67 | int MODULE_POWER; /* between -12 and 10 */ 68 | int MODULE_SQUELCH; /* between -128 and -45 */ 69 | int MODULE_OFFSET; /* between -2000 and 2000 */ 70 | int MODULE_PACKET_WAIT; /* wait 25 ms in reading from local G2 */ 71 | int TIMING_TIMEOUT_REMOTE_G2; /* 1 second */ 72 | int TIMING_PLAY_DELAY; 73 | int TIMING_PLAY_WAIT; 74 | bool MODULE_ACKNOWLEDGE; 75 | double TIMING_TIMEOUT_LOCAL_RPTR; 76 | bool LOG_DEBUG; 77 | bool LOG_QSO; 78 | int inactiveMax = 25; 79 | 80 | /* helper data */ 81 | unsigned char SND_TERM_ID; 82 | char RPTR_and_G[9]; 83 | char RPTR_and_MOD[9]; 84 | int serfd = -1; 85 | bool busy20000 = false; 86 | 87 | unsigned int space = 0; 88 | }; 89 | -------------------------------------------------------------------------------- /QnetDVRPTR.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright (C) 2020 by Thomas Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "UnixDgramSocket.h" 24 | #include "DStarDecode.h" 25 | #include "QnetTypeDefs.h" 26 | #include "Base.h" 27 | 28 | using tambevoicefec = unsigned char[9]; 29 | using tambevoice = unsigned char[6]; 30 | 31 | #define interleaveambe12(bp) { bp+=12; if (bp>71) bp -= 71; } 32 | #define CALL_SIZE 8 33 | #define GORLAY_X22 0x00400000 // vector representation of X^{22} 34 | #define GORLAY_X11 0x00000800 // vector representation of X^{11} 35 | #define GORLAY_MASK12 0xfffff800 // auxiliary vector for testing 36 | #define GORLAY_GENPOL 0x00000c75 // generator polinomial, g(x) 37 | 38 | class CQnetDVRPTR : public CModem 39 | { 40 | public: 41 | CQnetDVRPTR(int index) : CModem(index) {} 42 | ~CQnetDVRPTR() {} 43 | bool Initialize(const std::string &file); 44 | void Run(); 45 | void Close(); 46 | 47 | private: 48 | CDStarDecode decode; 49 | bool ReadConfig(const std::string &cfgFile); 50 | void readFrom20000(); 51 | bool check_serial(); 52 | void CleanCall(std::string &callsign); 53 | void ambefec_deinterleave(tambevoicefec result, const tambevoicefec voice); 54 | void ambefec_interleave(tambevoicefec result, const tambevoicefec raw_voice); 55 | void ambefec_regenerate(tambevoicefec voice); 56 | uint32_t get_syndrome_23127(uint32_t pattern); 57 | unsigned int gorlay_decode23127(unsigned int code); 58 | unsigned int gorlay_encode24128(unsigned int data); 59 | unsigned int gorlay_decode24128(unsigned int code); 60 | void calcPFCS(unsigned char *packet); 61 | char *cleanstr(char *Text); 62 | int open_port(char *dvrptr_device); 63 | int read_port(int *fd_ser, unsigned char *buffera); 64 | void send_ack(char *a_call, float ber); 65 | /*** BER stuff ***/ 66 | int ber_data[3]; 67 | int ber_errs; 68 | int num_dv_frames; 69 | int num_bit_errors; 70 | 71 | short block = 0; 72 | short old_seq_no = 0; 73 | 74 | short seq_no1 = 1; 75 | short seq_no2 = 1; 76 | short seq_no3 = 0; 77 | int fd_ser = -1; 78 | bool busy20000 = false; 79 | 80 | int rqst_count = 6; 81 | unsigned short streamid = 0x0; 82 | unsigned char start_Header[8]= {0xD0,0x03,0x00,0x16,0x01,0x00,0x00,0x00}; 83 | unsigned char ptt_off[8]= {0xD0,0x03,0x00,0x1A,0x01,0xff,0x00,0x00}; 84 | 85 | 86 | SDSVT Send_Network_Header; 87 | SDSVT Send_Network_Audio; 88 | 89 | int inactiveMax = 3200; 90 | 91 | unsigned char Send_Modem_Header[52]; 92 | 93 | unsigned char writevoice[24]; 94 | unsigned char writevoice1[24]; 95 | 96 | // Modem INIT 97 | unsigned char Modem_Init0[6]= {0xD0,0x01,0x00,0x11,0x00,0x00}; 98 | unsigned char Modem_Init1[7]= {0xD0,0x02,0x00,0x10,0x03,0x00,0x00}; // RX TX Enable 99 | unsigned char Modem_Init2[12]= {0xD0,0x07,0x00,0x14,0xC0,0x04,0x00,0x57,0x53,0x00,0x00,0x00}; // Modem Init 100 | unsigned char Modem_STATUS[6]= {0xD0,0x01,0x00,0x10,0x00,0x00}; // Status Abfragr 101 | unsigned char Modem_SERIAL[6]= {0xD0,0x01,0x00,0x12,0x00,0x00}; 102 | 103 | CUnixDgramWriter ToGate; 104 | CUnixDgramReader FromGate; 105 | 106 | std::string DVRPTR_SERIAL; 107 | char DVCALL[CALL_SIZE + 1]; 108 | char RPTR[CALL_SIZE + 1]; 109 | char DVRPTR_MOD = 'B'; 110 | int RF_AUDIO_Level = 10; 111 | bool DUPLEX = true; 112 | int ACK_DELAY = 200000; 113 | int DELAY_BETWEEN = 20000; 114 | bool RPTR_ACK = false; 115 | char ENABLE_RF[CALL_SIZE + 1]; 116 | char DISABLE_RF[CALL_SIZE + 1]; 117 | bool IS_ENABLED = true; 118 | bool RX_Inverse = true; 119 | bool TX_Inverse = true; 120 | int TX_DELAY; /* in milliseconds */ 121 | unsigned char SND_TERM_ID = 0x00; 122 | char DVCALL_and_G[9]; 123 | char DVCALL_and_MOD[9]; 124 | int REMOTE_TIMEOUT = 1; /* 1 second */ 125 | int RQST_COUNT = 6; 126 | u_int16_t streamid_raw = 0; 127 | char myRPT2[10]; //RX from HF RPT2 128 | char myRPT1[10]; //RX from HF RPT1 129 | char myUR[10]; 130 | char myCall[10]; 131 | char myCall2[10]; 132 | 133 | char cbuf[250]; 134 | 135 | SDSVT recv_buf; 136 | int InitCount; 137 | }; 138 | -------------------------------------------------------------------------------- /QnetGateway.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2020 by Thomas Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "IRCDDB.h" 24 | #include "QnetTypeDefs.h" 25 | #include "SEcho.h" 26 | #include "UnixDgramSocket.h" 27 | #include "aprs.h" 28 | #include "SockAddress.h" 29 | #include "QnetDB.h" 30 | #include "DStarDecode.h" 31 | #include "Base.h" 32 | #include "Location.h" 33 | 34 | #define MAXHOSTNAMELEN 64 35 | #define CALL_SIZE 8 36 | #define MAX_DTMF_BUF 32 37 | 38 | using STOREMOTEG2 = struct to_remote_g2_tag 39 | { 40 | unsigned short streamid; 41 | CSockAddress toDstar; 42 | time_t last_time; 43 | }; 44 | 45 | using STOREPEATER = struct torepeater_tag 46 | { 47 | // help with header re-generation 48 | SDSVT saved_hdr; // repeater format 49 | time_t last_time; 50 | unsigned char sequence; 51 | }; 52 | 53 | using SBANDTXT = struct band_txt_tag 54 | { 55 | unsigned short streamID; 56 | unsigned char flags[3]; 57 | std::string mycall, sfx, urcall, rpt1, rpt2, txt, dest_rptr; 58 | time_t last_time, gps_last_time; 59 | bool sent_key_on_msg, is_gps_sent; 60 | 61 | int num_dv_frames; 62 | int num_dv_silent_frames; 63 | int num_bit_errors; 64 | 65 | void Initialize() 66 | { 67 | streamID = 0x0U; 68 | last_time = gps_last_time = 0; 69 | is_gps_sent = sent_key_on_msg = false; 70 | num_dv_frames = num_dv_silent_frames = num_bit_errors = 0; 71 | flags[0] = flags[1] = flags[2] = 0x0U; 72 | mycall.clear(); 73 | sfx.clear(); 74 | urcall.clear(); 75 | rpt1.clear(); 76 | rpt2.clear(); 77 | txt.clear(); 78 | dest_rptr.clear(); 79 | } 80 | }; 81 | 82 | using SSD = struct sd_tag 83 | { 84 | unsigned char header[41]; 85 | unsigned char message[21]; 86 | unsigned char gps[256]; 87 | unsigned int ih, im, ig; 88 | unsigned char type; 89 | bool first; 90 | unsigned int size; 91 | void Init() { ih = im = ig = 0; first = true; } 92 | }; 93 | 94 | class CQnetGateway : public CBase 95 | { 96 | public: 97 | CQnetGateway(); 98 | bool Initialize(const std::string &path); 99 | void Run(); 100 | void Close(); 101 | 102 | private: 103 | // link type 104 | int link_family[3] = { AF_UNSPEC, AF_UNSPEC, AF_UNSPEC }; 105 | // network type 106 | int af_family[2] = { AF_UNSPEC, AF_UNSPEC }; 107 | 108 | int Index[3] = { -1, -1, -1 }; 109 | 110 | SPORTIP g2_external, g2_ipv6_external, ircddb[2]; 111 | 112 | CUnixDgramReader FromRemote, FromLink, FromModem[3]; 113 | CUnixDgramWriter ToLink, ToModem[3]; 114 | 115 | std::string OWNER, owner, FILE_DTMF, FILE_ECHOTEST, IRCDDB_PASSWORD[2], FILE_QNVOICE_FILE, DASH_SHOW_ORDER; 116 | 117 | bool GATEWAY_SEND_QRGS_MAP, GATEWAY_HEADER_REGEN, APRS_ENABLE, playNotInCache, showLastHeard; 118 | bool LOG_DEBUG, LOG_IRC, LOG_DTMF, LOG_QSO, IS_HF[3]; 119 | 120 | int DASH_REFRESH, TIMING_PLAY_WAIT, TIMING_PLAY_DELAY, TIMING_TIMEOUT_ECHO, TIMING_TIMEOUT_VOICEMAIL, TIMING_TIMEOUT_REMOTE_G2, TIMING_TIMEOUT_LOCAL_RPTR, dtmf_digit; 121 | 122 | unsigned int vPacketCount[3] = { 0, 0, 0 }; 123 | 124 | std::set findRoute; 125 | 126 | // data needed for aprs login and aprs beacon 127 | // RPTR defined in aprs.h 128 | SRPTR Rptr; 129 | 130 | // local repeater modules being recorded 131 | // This is for echotest and voicemail 132 | SECHO recd[3], vm[3]; 133 | SDSVT recbuf; // 56 or 27, max is 56 134 | 135 | // the streamids going to remote Gateways from each local module 136 | STOREMOTEG2 to_remote_g2[3]; // 0=A, 1=B, 2=C 137 | 138 | // input from remote G2 gateway 139 | int g2_sock[2] = { -1, -1 }; 140 | CSockAddress fromDstar; 141 | 142 | // Incoming data from remote systems 143 | // must be fed into our local repeater modules. 144 | STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C 145 | 146 | SDSVT end_of_audio, sdheader; 147 | 148 | // send packets to g2_link 149 | struct sockaddr_in plug; 150 | 151 | // for talking with the irc server 152 | CIRCDDB *ii[2]; 153 | // for handling APRS stuff 154 | CAPRS *aprs; 155 | // for parsign GPS slow data 156 | CLocation gps; 157 | 158 | // text coming from local repeater bands 159 | SBANDTXT band_txt[3]; // 0=A, 1=B, 2=C 160 | 161 | /* Used to validate MYCALL input */ 162 | std::regex preg; 163 | 164 | // database for the dashboard last heard section 165 | CQnetDB qnDB; 166 | 167 | // for bit error rate calcs 168 | CDStarDecode decode; 169 | 170 | // g2 data 171 | std::string lhcallsign[3], lhsfx[3]; 172 | unsigned char nextctrl[3] = { 0U, 0U, 0U }; 173 | std::string superframe[3]; 174 | 175 | // dtmf stuff 176 | int dtmf_buf_count[3]; 177 | char dtmf_buf[3][MAX_DTMF_BUF + 1]; 178 | int dtmf_last_frame[3]; 179 | unsigned int dtmf_counter[3]; 180 | 181 | bool VoicePacketIsSync(const unsigned char *text) const; 182 | int open_port(const SPORTIP *pip, int family); 183 | void calcPFCS(unsigned char *packet, int len); 184 | void GetIRCDataThread(const int i); 185 | int get_yrcall_rptr_from_cache(const int i, const std::string &call, std::string &rptr, std::string &gate, std::string &addr, char RoU); 186 | int get_yrcall_rptr(const std::string &call, std::string &rptr, std::string &gate, std::string &addr, char RoU); 187 | void PlayFileThread(SECHO &edata); 188 | void compute_aprs_hash(); 189 | void APRSBeaconThread(); 190 | bool Printable(unsigned char *string); 191 | void ProcessTimeouts(); 192 | void ProcessIncomingSD(const SDSVT &dsvt, const int source_sock); 193 | void ProcessOutGoingSD(const SDSVT &dsvt, const int mod); 194 | bool ProcessG2Msg(const unsigned char *data, const int mod, std::string &smrtgrp); 195 | void ProcessG2(const ssize_t g2buflen, SDSVT &g2buf, const int sock_source); 196 | void ProcessG2Header(const SDSVT &g2buf, const int source_sock); 197 | void ProcessModem(const ssize_t len, SDSVT &dsvt); 198 | bool Flag_is_ok(unsigned char flag); 199 | void UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters = ","); 200 | void PrintCallsigns(const std::string &key, const std::set &set); 201 | int FindIndex(const int i) const; 202 | 203 | // read configuration file 204 | bool ReadConfig(const std::string &path); 205 | 206 | void qrgs_and_maps(); 207 | 208 | void set_dest_rptr(const char mod, std::string &call); 209 | 210 | // for incoming slow header stuff; 211 | SSD sdin[4], sdout[3]; 212 | }; 213 | -------------------------------------------------------------------------------- /QnetITAP.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2020 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include "Random.h" // for streamid generation 28 | #include "UnixDgramSocket.h" 29 | #include "Base.h" 30 | 31 | #define CALL_SIZE 8 32 | #define IP_SIZE 15 33 | 34 | enum REPLY_TYPE 35 | { 36 | RT_TIMEOUT, 37 | RT_ERROR, 38 | RT_UNKNOWN, 39 | RT_HEADER, 40 | RT_DATA, 41 | RT_HEADER_ACK, 42 | RT_DATA_ACK, 43 | RT_PONG 44 | }; 45 | 46 | // Icom Terminal and Access Point Mode data structure 47 | #pragma pack(push, 1) 48 | using SITAP = struct itap_tag 49 | { 50 | unsigned char length; 51 | // 41 for header 52 | // 16 for voice 53 | unsigned char type; 54 | // 0x03U pong 55 | // 0x10U header from icom 56 | // 0x11U acknowledgment 57 | // 0x12U data from icom (it's EOT if voice.sequence bit 0x40 is set) 58 | // 0x13U acknowledgment 59 | // 0x20U header to icom 60 | // 0x21U header acknowledgment 61 | // 0x22U data to icom 62 | // 0x23U data acknowledgment 63 | union 64 | { 65 | struct 66 | { 67 | unsigned char flag[3]; 68 | unsigned char r2[8]; 69 | unsigned char r1[8]; 70 | unsigned char ur[8]; 71 | unsigned char my[8]; 72 | unsigned char nm[4]; 73 | } header; 74 | struct 75 | { 76 | unsigned char counter; // ordinal counter is reset with each header 77 | unsigned char sequence; // is modulo 21 78 | unsigned char ambe[9]; 79 | unsigned char text[3]; 80 | } voice; 81 | }; 82 | }; 83 | #pragma pack(pop) 84 | 85 | class CFrame 86 | { 87 | public: 88 | CFrame(const unsigned char *buf) 89 | { 90 | memcpy(&frame.length, buf, buf[0]); 91 | } 92 | 93 | CFrame(const CFrame &from) 94 | { 95 | memcpy(&frame.length, from.data(), from.size()); 96 | } 97 | 98 | ~CFrame() {} 99 | 100 | size_t size() const { return (size_t)frame.length; } 101 | 102 | const unsigned char *data() const { return &frame.length; } 103 | 104 | private: 105 | SITAP frame; 106 | }; 107 | 108 | class CQnetITAP : public CModem 109 | { 110 | public: 111 | // functions 112 | CQnetITAP(int mod) : CModem(mod) {} 113 | ~CQnetITAP() {} 114 | bool Initialize(const std::string &cfgfile); 115 | void Run(); 116 | void Close(); 117 | 118 | // data 119 | 120 | private: 121 | // functions 122 | bool ProcessGateway(const int len, const unsigned char *raw); 123 | bool ProcessITAP(const unsigned char *raw); 124 | int OpenITAP(); 125 | void SendToIcom(const unsigned char *buf); 126 | REPLY_TYPE GetITAPData(unsigned char *buf); 127 | void calcPFCS(const unsigned char *packet, unsigned char *pfcs); 128 | void DumpSerialPacket(const char *title, const unsigned char *); 129 | 130 | // read configuration file 131 | bool ReadConfig(const std::string &path); 132 | 133 | // config data 134 | char RPTR_MOD; 135 | std::string ITAP_DEVICE, RPTR; 136 | bool LOG_QSO, LOG_DEBUG, AP_MODE; 137 | 138 | // parameters 139 | int serfd; 140 | unsigned char tapcounter; 141 | 142 | // helpers 143 | CRandom random; 144 | 145 | // unix sockets 146 | CUnixDgramWriter ToGate; 147 | CUnixDgramReader FromGate; 148 | 149 | // Queue 150 | std::queue queue; 151 | bool acknowledged; 152 | }; 153 | -------------------------------------------------------------------------------- /QnetLink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2018-2019 by Thomas A. Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "QnetTypeDefs.h" 30 | #include "SEcho.h" 31 | #include "Random.h" 32 | #include "UnixDgramSocket.h" 33 | #include "UDPSocket.h" 34 | #include "UDPSocket.h" 35 | #include "Timer.h" 36 | #include "QnetDB.h" 37 | #include "Base.h" 38 | 39 | /*** version number must be x.xx ***/ 40 | #define CALL_SIZE 8 41 | #define IP_SIZE 15 42 | #define QUERY_SIZE 56 43 | #define MAXHOSTNAMELEN 64 44 | #define TIMEOUT 50 45 | #define LH_MAX_SIZE 39 46 | 47 | using SREFDSVT = struct refdsvt_tag 48 | { 49 | unsigned char head[2]; 50 | SDSVT dsvt; 51 | }; 52 | 53 | using STOREMOTE = struct to_remote_g2_tag 54 | { 55 | char cs[CALL_SIZE + 1]; 56 | CSockAddress addr; 57 | char from_mod, to_mod; 58 | short countdown; 59 | bool auto_link, is_connected; 60 | unsigned short in_streamid; // incoming from remote systems 61 | unsigned short out_streamid; // outgoing to remote systems 62 | }; 63 | 64 | // This is the data payload in the map: inbound_list 65 | // This is for inbound dongles 66 | using SINBOUND = struct inbound_tag 67 | { 68 | char call[CALL_SIZE + 1]; // the callsign of the remote 69 | CSockAddress addr; // IP and port of remote 70 | short countdown; // if countdown expires, the connection is terminated 71 | char mod; // A B C This user talked on this module 72 | char client; // dvap, dvdongle 73 | }; 74 | 75 | using STRACING = struct tracing_tag 76 | { 77 | unsigned short streamid; 78 | time_t last_time; // last time RF user talked 79 | }; 80 | 81 | 82 | class CQnetLink : public CBase 83 | { 84 | public: 85 | // functions 86 | CQnetLink(); 87 | ~CQnetLink(); 88 | bool Initialize(const std::string &path); 89 | void Run(); 90 | void Close(); 91 | private: 92 | // functions 93 | void ToUpper(std::string &s); 94 | void UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters = ","); 95 | void PrintCallsigns(const std::string &key, const std::set &set); 96 | void LoadGateways(const std::string &filename); 97 | void calcPFCS(unsigned char *packet, int len); 98 | bool ReadConfig(const std::string &path); 99 | bool srv_open(); 100 | void srv_close(); 101 | void g2link(const char from_mod, const char *call, const char to_mod); 102 | void send_heartbeat(); 103 | bool resolve_rmt(const char *name, const unsigned short port, CSockAddress &addr); 104 | void rptr_ack(int i); 105 | void PlayAudioNotifyThread(char *msg); 106 | void AudioNotifyThread(SECHO &edata); 107 | void RptrAckThread(char *arg); 108 | void ProcessXRF(unsigned char *buf, const int length); 109 | void ProcessDCS(unsigned char *buf, const int length); 110 | void ProcessREF(unsigned char *buf, const int length); 111 | void REFWrite(const void *buf, const size_t size, const CSockAddress &addr); 112 | void DCSWrite(const void *buf, const size_t size, const CSockAddress &addr); 113 | void XRFWrite(const void *buf, const size_t size, const CSockAddress &addr); 114 | 115 | /* configuration data */ 116 | std::string login_call, owner, to_g2_external_ip, gwys, qnvoice_file, announce_dir; 117 | bool only_admin_login, only_link_unlink, qso_details, log_debug, bool_rptr_ack, announce; 118 | bool dplus_authorize, dplus_reflectors, dplus_repeaters, dplus_priority, uses_ipv6; 119 | unsigned short rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port; 120 | int delay_between, delay_before; 121 | std::string link_at_startup[3]; 122 | unsigned int max_dongles, saved_max_dongles; 123 | int rf_inactivity_timer[3]; 124 | const unsigned char REF_ACK[3] = { 3, 96, 0 }; 125 | 126 | // the Key in this inbound_list map is the unique IP address of the remote 127 | std::map inbound_list; 128 | 129 | std::set admin, link_unlink_user, link_blacklist; 130 | 131 | std::map dt_lh_list; 132 | 133 | char notify_msg[3][64]; 134 | 135 | STOREMOTE to_remote_g2[3]; 136 | 137 | // broadcast for data arriving from xrf to local rptr 138 | struct brd_from_xrf_tag 139 | { 140 | unsigned short xrf_streamid; // streamid from xrf 141 | unsigned short rptr_streamid[2]; // generated streamid to rptr(s) 142 | } brd_from_xrf; 143 | SDSVT from_xrf_torptr_brd; 144 | short brd_from_xrf_idx; 145 | 146 | // broadcast for data arriving from local rptr to xrf 147 | struct brd_from_rptr_tag 148 | { 149 | unsigned short from_rptr_streamid; 150 | unsigned short to_rptr_streamid[2]; 151 | } brd_from_rptr; 152 | SDSVT fromrptr_torptr_brd; 153 | short brd_from_rptr_idx; 154 | 155 | STRACING tracing[3]; 156 | 157 | // input from remote 158 | //int xrf_g2_sock, ref_g2_sock, dcs_g2_sock; 159 | CUDPSocket XRFSock4, XRFSock6, DCSSock4, DCSSock6, REFSock4, REFSock6; 160 | CSockAddress fromDst4; 161 | 162 | // unix sockets to gateway 163 | CUnixDgramWriter ToGate; 164 | CUnixDgramReader FromGate; 165 | 166 | struct timeval tv; 167 | 168 | // Used to validate incoming donglers 169 | regex_t preg; 170 | 171 | unsigned char queryCommand[QUERY_SIZE]; 172 | 173 | // START: TEXT crap 174 | char dtmf_mycall[3][CALL_SIZE + 1]; 175 | bool new_group[3]; 176 | int header_type; 177 | bool GPS_seen[3]; 178 | unsigned char tmp_txt[3]; 179 | char *p_tmp2; 180 | // END: TEXT crap 181 | 182 | // this is used for the "dashboard and qso_details" to avoid processing multiple headers 183 | struct old_sid_tag 184 | { 185 | unsigned short sid; 186 | } old_sid[3]; 187 | 188 | CRandom Random; 189 | CQnetDB qnDB; 190 | std::vector speak; 191 | 192 | // used for processing loop 193 | const unsigned char endbytes[6] = { 0x55U, 0x55U, 0x55U, 0x55U, 0xC8U, 0x7AU }; 194 | time_t tnow; 195 | unsigned char dcs_seq[3] = { 0x00, 0x00, 0x00 }; 196 | struct 197 | { 198 | char mycall[9]; 199 | char sfx[5]; 200 | unsigned int dcs_rptr_seq; 201 | } rptr_2_dcs[3] = 202 | { 203 | {" ", " ", 0}, 204 | {" ", " ", 0}, 205 | {" ", " ", 0} 206 | }; 207 | struct 208 | { 209 | char mycall[9]; 210 | char sfx[5]; 211 | unsigned int dcs_rptr_seq; 212 | } ref_2_dcs[3] = 213 | { 214 | {" ", " ", 0}, 215 | {" ", " ", 0}, 216 | {" ", " ", 0} 217 | }; 218 | struct 219 | { 220 | char mycall[9]; 221 | char sfx[5]; 222 | unsigned int dcs_rptr_seq; 223 | } xrf_2_dcs[3] = 224 | { 225 | {" ", " ", 0}, 226 | {" ", " ", 0}, 227 | {" ", " ", 0} 228 | }; 229 | }; 230 | -------------------------------------------------------------------------------- /QnetModem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2021 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include "Random.h" // for streamid generation 27 | #include "UnixDgramSocket.h" 28 | #include "QnetTypeDefs.h" 29 | #include "Timer.h" 30 | #include "Base.h" 31 | 32 | #define CALL_SIZE 8 33 | #define IP_SIZE 15 34 | 35 | enum class EModemResponse 36 | { 37 | ack, 38 | nack, 39 | timeout, 40 | error, 41 | header, 42 | data, 43 | lost, 44 | eot, 45 | status, 46 | version 47 | }; 48 | 49 | enum class EHardwareType 50 | { 51 | mmdvm, 52 | dvmega, 53 | zumspot, 54 | hs_hat, 55 | hs_dual_hat, 56 | nano_hs, 57 | nano_dv, 58 | mmdvm_hs, 59 | unknown 60 | }; 61 | 62 | // Icom Terminal and Access Point Mode data structure 63 | #pragma pack(push, 1) 64 | using SVERSION = struct version_tag 65 | { 66 | unsigned char start; 67 | unsigned char length; 68 | unsigned char type; 69 | unsigned char protocol; 70 | unsigned char version[251]; 71 | }; 72 | 73 | using SMODEM = struct mmodem_tag 74 | { 75 | unsigned char start; // always 0xEOU 76 | unsigned char length; // 3 - 255 77 | unsigned char type; 78 | // 0x70U acknowledge from modem, ACK 79 | // 0x7FU error from modem, NACK 80 | // 0x00U version 81 | // 0x01U status 82 | // 0x02U configure 83 | // 0x03U mode 84 | // 0x04U frequency 85 | // 0x10U header 86 | // 0x11U data 87 | // 0x12U transmission lost 88 | // 0x13U transmission end 89 | union 90 | { 91 | unsigned char ack; // the type being acknowledged 92 | unsigned char mode; // 0 idle, 1 dstar, 2 dmr, 3 ysf, 99 calibration 93 | struct 94 | { 95 | unsigned char ack; // the type being acknowledged 96 | unsigned char reason; // reason for the NAK 97 | // 1 - invalid command 98 | // 2 - wrong mode 99 | // 3 - command too long 100 | // 4 - data incorrect 101 | // 5 - Not enough buffer space 102 | } nack; 103 | // don't want to inflate the struct size, so it's here for reference only 104 | //struct { 105 | // unsigned char protocol_version; 106 | // unsigned char version[250]; 107 | //} version; 108 | struct 109 | { 110 | unsigned char modes; // 0x1U dstar | 0x2 dmr | 0x4 system fusion 111 | unsigned char status; // 0 idle, 1 dstar, 2 dmr, 3 system fusion, 99 calibration 112 | unsigned char flags; // 0x1 Tx on, 0x2 adc overflow 113 | unsigned char dsrsize; // dstar buffersize 114 | unsigned char dm1size; // drm timeslot 1 buffersize 115 | unsigned char dm2size; // dmr timeslot 2 buffersize 116 | unsigned char ysfsize; // ysf buffersize 117 | } status; 118 | struct 119 | { 120 | unsigned char flags; // 0x1 rx 0x2 tx 0x4 ptt 0x8 ysf lodev 0x10 debug 0x80 not duplex 121 | unsigned char mode; // 0x1 dstar 0x2 drm 0x4 ysf 0x8 p25 0x10 nxdx 0x20 pocsag 122 | unsigned char tx_delay; // tx delay in 10 millisecond increments 123 | unsigned char init_mode; // inital state 0 idle 1 dstar 2 dmr 3 ysf 99 calibration 124 | unsigned char rx_level; // rx input level 0-255 125 | unsigned char cw_tx_level; // cw tx output 126 | unsigned char color; // dmr color 0-15 127 | unsigned char drm_delay; 128 | unsigned char osc_offset; // 128U 129 | unsigned char dstar_tx_level; 130 | unsigned char dmr_tx_level; 131 | unsigned char ysf_tx_level; 132 | unsigned char p25_tx_level; 133 | unsigned char tx_dc_offset; 134 | unsigned char rx_dc_offset; 135 | unsigned char nxdn_tx_level; 136 | unsigned char ysf_tx_hang; 137 | unsigned char pocsag_tx; 138 | } config; 139 | struct 140 | { 141 | unsigned char zero; // should be zero; 142 | uint32_t rx; // receive frequency 143 | uint32_t tx; // transmitter frequency 144 | unsigned char level; // rf level 145 | uint32_t ps; // pocsag frequency, default 433000000U 146 | } frequency; 147 | struct 148 | { 149 | unsigned char flag[3]; 150 | unsigned char r2[8]; 151 | unsigned char r1[8]; 152 | unsigned char ur[8]; 153 | unsigned char my[8]; 154 | unsigned char nm[4]; 155 | unsigned char pfcs[2]; 156 | } header; 157 | struct 158 | { 159 | unsigned char ambe[9]; 160 | unsigned char text[3]; 161 | } voice; 162 | }; 163 | }; 164 | #pragma pack(pop) 165 | 166 | class CFrame 167 | { 168 | public: 169 | CFrame(const unsigned char *buf) 170 | { 171 | memcpy(&frame.start, buf, buf[1]); 172 | } 173 | 174 | CFrame(const CFrame &from) 175 | { 176 | memcpy(&frame.start, from.data(), from.size()); 177 | } 178 | 179 | CFrame &operator=(const CFrame &from) 180 | { 181 | memcpy(&frame.start, from.data(), from.size()); 182 | return *this; 183 | } 184 | 185 | ~CFrame() {} 186 | 187 | size_t size() const { return (size_t)frame.length; } 188 | 189 | const unsigned char *data() const { return &frame.start; } 190 | unsigned char type() { return frame.type; } 191 | 192 | private: 193 | SMODEM frame; 194 | }; 195 | 196 | class CQnetModem : public CModem 197 | { 198 | public: 199 | // functions 200 | CQnetModem(int mod) : CModem(mod), dstarSpace(0) {} 201 | ~CQnetModem() {} 202 | bool Initialize(const std::string &cfgfile); 203 | void Run(); 204 | void Close(); 205 | 206 | private: 207 | unsigned int dstarSpace; 208 | 209 | // functions 210 | bool VoicePacketIsSync(const unsigned char *); 211 | void ProcessGateway(const SDSVT &dsvt); 212 | bool ProcessModem(const SMODEM &frame); 213 | int OpenModem(); 214 | int SendToModem(const unsigned char *buf); 215 | EModemResponse GetModemData(unsigned char *buf, unsigned int size); 216 | bool GetVersion(); 217 | bool GetBufferSize(); 218 | bool SetFrequency(); 219 | bool SetConfiguration(); 220 | 221 | // read configuration file 222 | bool ReadConfig(const std::string &path); 223 | 224 | // config data 225 | char RPTR_MOD; 226 | std::string MODEM_DEVICE, RPTR; 227 | double TX_FREQUENCY, RX_FREQUENCY, TX_OFFSET, RX_OFFSET, packet_wait; 228 | int TX_DELAY, RX_LEVEL, TX_LEVEL, RF_LEVEL, PACKET_WAIT; 229 | bool DUPLEX, RX_INVERT, TX_INVERT, PTT_INVERT, LOG_QSO, LOG_DEBUG; 230 | 231 | // parameters 232 | EHardwareType hardwareType; 233 | int serfd; 234 | 235 | 236 | // helpers 237 | CRandom random; 238 | CTimer PacketWait; 239 | 240 | // unix sockets 241 | CUnixDgramWriter ToGate; 242 | CUnixDgramReader FromGate; 243 | 244 | // Queue 245 | std::queue queue; 246 | }; 247 | -------------------------------------------------------------------------------- /QnetRelay.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "UnixDgramSocket.h" 26 | #include "Base.h" 27 | 28 | #define CALL_SIZE 8 29 | #define IP_SIZE 15 30 | 31 | class CQnetRelay : public CModem 32 | { 33 | public: 34 | // functions 35 | CQnetRelay(int mod) : CModem(mod), seed(time(NULL)), COUNTER(0) {} 36 | ~CQnetRelay() {} 37 | bool Initialize(const std::string &cfgfile); 38 | void Run(); 39 | void Close(); 40 | 41 | private: 42 | // functions 43 | bool ProcessGateway(const int len, const unsigned char *raw); 44 | bool ProcessMMDVM(const int len, const unsigned char *raw); 45 | int OpenSocket(const std::string &address, unsigned short port); 46 | int SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port); 47 | 48 | // read configuration file 49 | bool ReadConfig(const std::string &); 50 | 51 | // Unix sockets 52 | CUnixDgramWriter ToGate; 53 | CUnixDgramReader FromGate; 54 | 55 | // config data 56 | char RPTR_MOD; 57 | std::string MMDVM_INTERNAL_IP, MMDVM_TARGET_IP; 58 | unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT; 59 | bool log_qso, IS_DSTARREPEATER; 60 | 61 | // parameters 62 | int msock; 63 | unsigned int seed; 64 | unsigned short COUNTER; 65 | }; 66 | -------------------------------------------------------------------------------- /QnetTypeDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2017-2021 by Thomas Early, N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | // for communicating with the g2 gateway on the internal port 21 | #pragma pack(push, 1) // used internally by Icom stacks 22 | using SDSTR = struct dstr_tag 23 | { 24 | unsigned char pkt_id[4]; // 0 "DSTR" 25 | unsigned short counter; // 4 26 | unsigned char flag[3]; // 6 { 0x73, 0x12, 0x00 } 27 | unsigned char remaining; // 9 the number of bytes left in the packet 28 | union 29 | { 30 | struct 31 | { 32 | unsigned char mycall[8]; // 10 33 | unsigned char rpt[8]; // 18 34 | } spkt; // total 26 35 | struct 36 | { 37 | unsigned char icm_id; // 10 38 | unsigned char dst_rptr_id; // 11 39 | unsigned char snd_rptr_id; // 12 40 | unsigned char snd_term_id; // 13 41 | unsigned short streamid; // 14 42 | unsigned char ctrl; // 16 sequence number hdr=0, voice%21, end|=0x40 43 | union 44 | { 45 | struct 46 | { 47 | unsigned char flag[3]; // 17 48 | unsigned char r2[8]; // 20 49 | unsigned char r1[8]; // 28 50 | unsigned char ur[8]; // 36 51 | unsigned char my[8]; // 44 52 | unsigned char nm[4]; // 52 53 | unsigned char pfcs[2]; // 56 54 | } hdr; // total 58 55 | union 56 | { 57 | struct 58 | { 59 | unsigned char voice[9]; // 17 60 | unsigned char text[3]; // 26 61 | } vasd; // total 29 62 | struct 63 | { 64 | unsigned char UNKNOWN[3]; // 17 not sure what this is, but g2_ doesn't seem to need it 65 | unsigned char voice[9]; // 20 66 | unsigned char text[3]; // 29 67 | } vasd1; // total 32 68 | }; 69 | }; 70 | } vpkt; 71 | }; 72 | }; 73 | #pragma pack(pop) 74 | 75 | // for the g2 external port and between QnetGateway programs 76 | #pragma pack(push, 1) 77 | using SDSVT = struct dsvt_tag 78 | { 79 | unsigned char title[4]; // 0 "DSVT" 80 | unsigned char config; // 4 0x10 is hdr 0x20 is vasd 81 | unsigned char flaga[3]; // 5 zeros 82 | unsigned char id; // 8 0x20 83 | unsigned char flagb[3]; // 9 0x0 0x1 (A:0x3 B:0x1 C:0x2) 84 | unsigned short streamid;// 12 85 | unsigned char ctrl; // 14 hdr: 0x80 vsad: framecounter (mod 21) 86 | union 87 | { 88 | struct // index 89 | { 90 | unsigned char flag[3]; // 15 91 | unsigned char rpt1[8]; // 18 92 | unsigned char rpt2[8]; // 26 93 | unsigned char urcall[8];// 34 94 | unsigned char mycall[8];// 42 95 | unsigned char sfx[4]; // 50 96 | unsigned char pfcs[2]; // 54 97 | } hdr; // total 56 98 | struct 99 | { 100 | unsigned char voice[9]; // 15 101 | unsigned char text[3]; // 24 102 | } vasd; // voice and slow data total 27 103 | struct 104 | { 105 | unsigned char voice[9]; // 15 106 | unsigned char textend[6]; // 24 107 | } vend; // voice and end seq total 32 (for DPlus) 108 | }; 109 | }; 110 | #pragma pack(pop) 111 | 112 | // for mmdvm 113 | #pragma pack(push, 1) 114 | using SDSRP = struct dsrp_tag // offset size 115 | { 116 | unsigned char title[4]; // "DSRP" 0 117 | unsigned char tag; // Poll : 0xA 4 118 | // Header : busy ? 0x22 : 0x20 119 | // Voice : busy ? 0x23 : 0x21 120 | union 121 | { 122 | unsigned char poll_msg[59]; // space for text 5 variable, max is 64, including trailing null 123 | struct 124 | { 125 | unsigned short id; // random id number 5 126 | unsigned char seq; // 0x0 7 127 | unsigned char flag[3]; // 0x80 Dstar Data 8 128 | // 0x40 Dstar Repeater 129 | // 0x01 Dstar Relay Unavailable 130 | unsigned char r2[8]; // Repeater 2 11 131 | unsigned char r1[8]; // Repeater 1 19 132 | unsigned char ur[8]; // Your Call 27 133 | unsigned char my[8]; // My Call 35 134 | unsigned char nm[4]; // Name 43 135 | unsigned char pfcs[2]; // checksum 47 49 136 | } header; 137 | struct 138 | { 139 | unsigned short id; // random id number 5 140 | unsigned char seq; // sequence from 0 to 0x14 7 141 | // if end then sequence |= 0x40 142 | unsigned char err; // # of errors? 8 143 | unsigned char ambe[12]; // voice + slow data 9 21 144 | } voice; 145 | struct 146 | { 147 | unsigned short id; // random id number 5 148 | unsigned char seq; // sequence from 0 to 0x14 7 149 | // if end then sequence |= 0x40 150 | unsigned char err; // # of errors? 8 151 | unsigned char ambe[15]; // voice + slow data 9 24 152 | } endvoice; 153 | }; 154 | }; 155 | #pragma pack(pop) 156 | 157 | #pragma pack(push, 1) 158 | using SLINKFAMILY = struct link_family_tag 159 | { 160 | char title[4]; 161 | int family[3]; 162 | }; 163 | #pragma pack(pop) 164 | -------------------------------------------------------------------------------- /QnetVoice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 by Scott Lawson KI4LKF 3 | * Copyright (C) 2018 by Thomas A. Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include "QnetConfigure.h" 26 | 27 | bool isamod[3] = { false, false, false }; 28 | std::string announce_dir; 29 | std::string qnvoice_file; 30 | 31 | /* process configuration file */ 32 | bool read_config(const char *cfgFile) 33 | { 34 | CQnetConfigure cfg; 35 | 36 | printf("Reading file %s\n", cfgFile); 37 | if (cfg.Initialize(cfgFile)) 38 | return true; 39 | 40 | for (int m=0; m<3; m++) 41 | { 42 | std::string path("module_"); 43 | path.append(std::to_string(m)); 44 | std::string type; 45 | if (cfg.KeyExists(path)) 46 | { 47 | cfg.GetValue(path, "", type, 1, 16); 48 | if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "itap")) 49 | { 50 | printf("module type '%s' is invalid\n", type.c_str()); 51 | return true; 52 | } 53 | isamod[m] = true; 54 | } 55 | } 56 | 57 | std::string path("file_"); 58 | cfg.GetValue(path+"announce_dir", "", announce_dir, 2, FILENAME_MAX); 59 | cfg.GetValue(path+"qnvoice_file", "", qnvoice_file, 2, FILENAME_MAX); 60 | 61 | return false; 62 | } 63 | 64 | void ToUpper(std::string &str) 65 | { 66 | for (unsigned int i=0; i \n", argv[0]); 78 | printf("Where...\n"); 79 | printf(" is one of your modules: A, B or C\n"); 80 | printf(" is an installed voice file in the configured\n"); 81 | printf(" directory, for example \"unlinked.dat\"\n"); 82 | printf(" is an up to 20-character text message\n"); 83 | return 0; 84 | } 85 | char module = argv[1][0]; 86 | 87 | std::string cfgfile(CFG_DIR); 88 | cfgfile += "/qn.cfg"; 89 | if (read_config(cfgfile.c_str())) 90 | return 1; 91 | 92 | if (islower(module)) 93 | module = toupper(module); 94 | if ((module != 'A') && (module != 'B') && (module != 'C')) 95 | { 96 | printf("module must be one of A B C\n"); 97 | return 1; 98 | } 99 | 100 | char pathname[FILENAME_MAX]; 101 | snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]); 102 | 103 | FILE *fp = fopen(pathname, "rb"); 104 | if (!fp) 105 | { 106 | printf("Failed to find file %s for reading\n", pathname); 107 | return 1; 108 | } 109 | fclose(fp); 110 | 111 | memset(RADIO_ID, '_', 20); 112 | RADIO_ID[20] = '\0'; 113 | 114 | unsigned int len = strlen(argv[3]); 115 | strncpy(RADIO_ID, argv[3], len > 20 ? 20 : len); 116 | for (int i=0; i<20; i++) 117 | if (isspace(RADIO_ID[i])) 118 | RADIO_ID[i] = '_'; 119 | 120 | fp = fopen(qnvoice_file.c_str(), "w"); 121 | if (fp) 122 | { 123 | fprintf(fp, "%c_%s_%s\n", module, argv[2], RADIO_ID); 124 | fclose(fp); 125 | } 126 | else 127 | { 128 | printf("Failed to open %s for writing", qnvoice_file.c_str()); 129 | return 1; 130 | } 131 | 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QnetGateway 2 | =========== 3 | 4 | The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1. It is *incredibly easy* to build and install the system. 5 | 6 | QnetGateway includes a dashboard with a last heard section. The lastheard section uses SQLite3, a light-weight database, so you will need a package to compile the gateway: 7 | 8 | ``` 9 | sudo apt install libsqlite3-dev 10 | ``` 11 | 12 | If you are going to install the dashboard, you need several libraries for php: 13 | 14 | ``` 15 | sudo apt install -y php-common php-fpm sqlite3 php-sqlite3 dnsutils 16 | ``` 17 | 18 | Be sure to read the DASHBOARD.README for more information. 19 | 20 | QnetGateway is dual-stack capable. This means it can simultaneously connect to ircv4.openquad.net, which is IPv4 based (using 32-bit internet addresses) and to ircv6.openquad.net which is IPv6 based (using 128-bit internet address). If your hot-spot/reapeater has IPv6 access you can enable dual-stack operation (it's IPv4-only by default) and then take advantage of direct world-routable address. The potential benefit of IPv6 to routing is significant. 21 | 22 | The QnetGateway program includes support for Icom's Terminal Mode and Access Point mode. For more information, Terminal Mode turns off the RF portion of you radio and just uses the AMBE vocoder to convert between audio and AMBE data and then sends and receives that data through a USB serial cable. Access Point mode turns your Icom radio into a high power, simplex hot-spot. 23 | 24 | QnetGateway supports MMDVM modems directly, without the need for MMDVMHost. This is for hams that want to use their MMDVM devices and create a hot-spot for D-Star mode only. (You still can talk to your friends on other modes by gathering at multi-mode reflectors, like the QuadNet Array!) 25 | 26 | For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the CONFIG+INSTALL file. To build QnetGateway for an Icom Repeater Stack, I have another repo at QnetIcomGateway. Detailed information is available there. 27 | 28 | To get started with an MMDVM-modem, DVAP, DVRPTR or Icom Terminal and/or Access Point system, clone this software to your Linux device: 29 | 30 | ``` 31 | git clone https://github.com/n7tae/QnetGateway.git 32 | ``` 33 | 34 | Then look to the MMDVM.README or the CONFIG+INSTALL file for more information. 35 | 36 | QnetGateway includes a "remote control" program, called `qnremote`. After you build and install the system, type `qnremote` for a prompt on how to use it. Using this and cron, it's possible to setup schedules where you system will automatically link up to a reflector, or subscribe to a Routing Group. For More information, see DTMF+REMOTE.README. 37 | 38 | For other details of interesting things QnetGatway can do, see the OPERATING file. For example, with QnetGateway, you can execute up to 36 different Linux scripts from you radio. Two scripts are include: 39 | 40 | ``` 41 | YourCall = " HX" will halt your system. 42 | YourCall = " RX" will reboot your system. 43 | YourCall = " GX" will restart QnetGateway. 44 | ``` 45 | 46 | QnetGateway is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. QnetGateway is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more details. 47 | 48 | Many thanks go to **Colby W1BSB**, **Will W4WWM** and **Carty KA2Y** for recent help, suggestions, discussion and criticisms of the Qnet*/MMDVMHost phase of this long-term project! Also thanks to Jonathan G4KLX for MMDVMHost. It gave QnetGateway access to a large number of D-Star compatible modems! 49 | 50 | 73 51 | 52 | Tom 53 | 54 | N7TAE (at) arrl (dot) net 55 | -------------------------------------------------------------------------------- /Random.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2019 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | class CRandom 25 | { 26 | public: 27 | CRandom() { srandom(getpid()); } 28 | 29 | ~CRandom() {} 30 | 31 | unsigned short NewStreamID() 32 | { 33 | unsigned short r = 0; 34 | while (0 == r) 35 | r = 0xffffU & random(); 36 | return r; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /SEcho.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2018-2020 by Thomas Early, N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include "QnetTypeDefs.h" 23 | 24 | using SECHO = struct echo_tag 25 | { 26 | bool is_linked; 27 | time_t last_time; 28 | unsigned short streamid; 29 | int fd; 30 | char message[24]; 31 | SDSVT header; // only used in qnlink (qngateway writes the header to the file) 32 | char file[FILENAME_MAX + 1]; 33 | }; 34 | -------------------------------------------------------------------------------- /SockAddress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2019-2020 by Thomas Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | class CSockAddress 29 | { 30 | public: 31 | CSockAddress() 32 | { 33 | Clear(); 34 | } 35 | 36 | CSockAddress(const int family, const unsigned short port = 0, const char *address = NULL) 37 | { 38 | Initialize(family, port, address); 39 | } 40 | 41 | ~CSockAddress() {} 42 | 43 | void Initialize(const int family, const uint16_t port = 0U, const char *address = NULL) 44 | { 45 | Clear(); 46 | addr.ss_family = family; 47 | if (AF_INET == family) 48 | { 49 | auto addr4 = (struct sockaddr_in *)&addr; 50 | addr4->sin_port = htons(port); 51 | if (address) 52 | { 53 | if (0 == strncasecmp(address, "loc", 3)) 54 | inet_pton(AF_INET, "127.0.0.1", &(addr4->sin_addr)); 55 | else if (0 == strncasecmp(address, "any", 3)) 56 | inet_pton(AF_INET, "0.0.0.0", &(addr4->sin_addr)); 57 | else if (address) 58 | { 59 | if (1 > inet_pton(AF_INET, address, &(addr4->sin_addr))) 60 | std::cerr << "Address Initialization Error: '" << address << "' is not a valdid IPV4 address!" << std::endl; 61 | } 62 | } 63 | } 64 | else if (AF_INET6 == family) 65 | { 66 | auto addr6 = (struct sockaddr_in6 *)&addr; 67 | addr6->sin6_port = htons(port); 68 | if (address) 69 | { 70 | if (0 == strncasecmp(address, "loc", 3)) 71 | inet_pton(AF_INET6, "::1", &(addr6->sin6_addr)); 72 | else if (0 == strncasecmp(address, "any", 3)) 73 | inet_pton(AF_INET6, "::", &(addr6->sin6_addr)); 74 | else if (address) 75 | { 76 | if (1 > inet_pton(AF_INET6, address, &(addr6->sin6_addr))) 77 | std::cerr << "Address Initialization Error: '" << address << "' is not a valid IPV6 address!" << std::endl; 78 | } 79 | } 80 | } 81 | else 82 | std::cerr << "Error: Wrong address family type:" << family << " for [" << (address ? address : "NULL") << "]:" << port << std::endl; 83 | } 84 | 85 | CSockAddress &operator=(const CSockAddress &from) 86 | { 87 | Clear(); 88 | if (AF_INET == from.addr.ss_family) 89 | memcpy(&addr, &from.addr, sizeof(struct sockaddr_in)); 90 | else 91 | memcpy(&addr, &from.addr, sizeof(struct sockaddr_in6)); 92 | strcpy(straddr, from.straddr); 93 | return *this; 94 | } 95 | 96 | bool operator==(const CSockAddress &rhs) const // doesn't compare ports, only addresses and families 97 | { 98 | if (addr.ss_family != rhs.addr.ss_family) 99 | return false; 100 | if (AF_INET == addr.ss_family) 101 | { 102 | auto l = (struct sockaddr_in *)&addr; 103 | auto r = (struct sockaddr_in *)&rhs.addr; 104 | return (l->sin_addr.s_addr == r->sin_addr.s_addr); 105 | } 106 | else if (AF_INET6 == addr.ss_family) 107 | { 108 | auto l = (struct sockaddr_in6 *)&addr; 109 | auto r = (struct sockaddr_in6 *)&rhs.addr; 110 | return (0 == memcmp(&(l->sin6_addr), &(r->sin6_addr), sizeof(struct in6_addr))); 111 | } 112 | return false; 113 | } 114 | 115 | bool operator!=(const CSockAddress &rhs) const // doesn't compare ports, only addresses and families 116 | { 117 | if (addr.ss_family != rhs.addr.ss_family) 118 | return true; 119 | if (AF_INET == addr.ss_family) 120 | { 121 | auto l = (struct sockaddr_in *)&addr; 122 | auto r = (struct sockaddr_in *)&rhs.addr; 123 | return (l->sin_addr.s_addr != r->sin_addr.s_addr); 124 | } 125 | else if (AF_INET6 == addr.ss_family) 126 | { 127 | auto l = (struct sockaddr_in6 *)&addr; 128 | auto r = (struct sockaddr_in6 *)&rhs.addr; 129 | return (0 != memcmp(&(l->sin6_addr), &(r->sin6_addr), sizeof(struct in6_addr))); 130 | } 131 | return true; 132 | } 133 | 134 | bool AddressIsZero() const 135 | { 136 | if (AF_INET == addr.ss_family) 137 | { 138 | auto addr4 = (struct sockaddr_in *)&addr; 139 | return (addr4->sin_addr.s_addr == 0U); 140 | } 141 | else 142 | { 143 | auto addr6 = (struct sockaddr_in6 *)&addr; 144 | for (unsigned int i=0; i<16; i++) 145 | { 146 | if (addr6->sin6_addr.s6_addr[i]) 147 | return false; 148 | } 149 | return true; 150 | } 151 | } 152 | 153 | void ClearAddress() 154 | { 155 | if (AF_INET == addr.ss_family) 156 | { 157 | auto addr4 = (struct sockaddr_in *)&addr; 158 | addr4->sin_addr.s_addr = 0U; 159 | strcpy(straddr, "0.0.0.0"); 160 | } 161 | else 162 | { 163 | auto addr6 = (struct sockaddr_in6 *)&addr; 164 | memset(&(addr6->sin6_addr.s6_addr), 0, 16); 165 | strcpy(straddr, "::"); 166 | } 167 | } 168 | 169 | const char *GetAddress() const 170 | { 171 | if (straddr[0]) 172 | return straddr; 173 | if (AF_INET == addr.ss_family) 174 | { 175 | auto addr4 = (struct sockaddr_in *)&addr; 176 | inet_ntop(AF_INET, &(addr4->sin_addr), straddr, INET6_ADDRSTRLEN); 177 | } 178 | else if (AF_INET6 == addr.ss_family) 179 | { 180 | auto addr6 = (struct sockaddr_in6 *)&addr; 181 | inet_ntop(AF_INET6, &(addr6->sin6_addr), straddr, INET6_ADDRSTRLEN); 182 | } 183 | else 184 | { 185 | std::cerr << "Unknown socket family: " << addr.ss_family << std::endl; 186 | } 187 | return straddr; 188 | } 189 | 190 | int GetFamily() const 191 | { 192 | return addr.ss_family; 193 | } 194 | 195 | unsigned short GetPort() const 196 | { 197 | if (AF_INET == addr.ss_family) 198 | { 199 | auto addr4 = (struct sockaddr_in *)&addr; 200 | return ntohs(addr4->sin_port); 201 | } 202 | else if (AF_INET6 == addr.ss_family) 203 | { 204 | auto addr6 = (struct sockaddr_in6 *)&addr; 205 | return ntohs(addr6->sin6_port); 206 | } 207 | else 208 | return 0; 209 | } 210 | 211 | void SetPort(const uint16_t newport) 212 | { 213 | if (AF_INET == addr.ss_family) 214 | { 215 | auto addr4 = (struct sockaddr_in *)&addr; 216 | addr4->sin_port = htons(newport); 217 | } 218 | else if (AF_INET6 == addr.ss_family) 219 | { 220 | auto addr6 = (struct sockaddr_in6 *)&addr; 221 | addr6->sin6_port = htons(newport); 222 | } 223 | } 224 | 225 | struct sockaddr *GetPointer() 226 | { 227 | memset(straddr, 0, INET6_ADDRSTRLEN); // things might change 228 | return (struct sockaddr *)&addr; 229 | } 230 | 231 | const struct sockaddr *GetCPointer() const 232 | { 233 | return (const struct sockaddr *)&addr; 234 | } 235 | 236 | size_t GetSize() const 237 | { 238 | if (AF_INET == addr.ss_family) 239 | return sizeof(struct sockaddr_in); 240 | else 241 | return sizeof(struct sockaddr_in6); 242 | } 243 | 244 | void Clear() 245 | { 246 | memset(&addr, 0, sizeof(struct sockaddr_storage)); 247 | memset(straddr, 0, INET6_ADDRSTRLEN); 248 | } 249 | 250 | operator const char *() const { return GetAddress(); } 251 | 252 | friend std::ostream &operator<<(std::ostream &stream, const CSockAddress &addr) 253 | { 254 | const char *sz = addr; 255 | if (AF_INET6 == addr.GetFamily()) 256 | stream << "[" << sz << "]"; 257 | else 258 | stream << sz; 259 | auto port = addr.GetPort(); 260 | if (port) 261 | stream << ":" << port; 262 | return stream; 263 | } 264 | 265 | private: 266 | struct sockaddr_storage addr; 267 | mutable char straddr[INET6_ADDRSTRLEN]; 268 | }; 269 | -------------------------------------------------------------------------------- /TCPReaderWriterClient.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX 3 | * Copyright (C) 2019 by Thomas A. Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include "TCPReaderWriterClient.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | CTCPReaderWriterClient::CTCPReaderWriterClient(const std::string &address, int family, const std::string &port) : 28 | m_address(address), 29 | m_family(family), 30 | m_port(port), 31 | m_fd(-1) 32 | { 33 | } 34 | 35 | CTCPReaderWriterClient::CTCPReaderWriterClient() : m_fd(-1) 36 | { 37 | } 38 | 39 | CTCPReaderWriterClient::~CTCPReaderWriterClient() 40 | { 41 | } 42 | 43 | bool CTCPReaderWriterClient::Open(const std::string &address, int family, const std::string &port) 44 | { 45 | m_address = address; 46 | m_family = family; 47 | m_port = port; 48 | 49 | return Open(); 50 | } 51 | 52 | bool CTCPReaderWriterClient::Open() 53 | { 54 | if (m_fd != -1) 55 | { 56 | fprintf(stderr, "ERROR: port for '%s' is already open!\n", m_address.c_str()); 57 | return true; 58 | } 59 | 60 | if (0 == m_address.size() || 0 == m_port.size() || 0 == std::stoul(m_port)) 61 | { 62 | fprintf(stderr, "ERROR: '[%s]:%s' is malformed!\n", m_address.c_str(), m_port.c_str()); 63 | return true; 64 | } 65 | 66 | if (AF_INET!=m_family && AF_INET6!=m_family && AF_UNSPEC!=m_family) 67 | { 68 | fprintf(stderr, "ERROR: family must be AF_INET, AF_INET6 or AF_UNSPEC\n"); 69 | return true; 70 | } 71 | 72 | struct addrinfo hints; 73 | memset(&hints, 0, sizeof(struct addrinfo)); 74 | hints.ai_family = AF_UNSPEC; 75 | hints.ai_socktype = SOCK_STREAM; 76 | //hints.ai_flags = AI_PASSIVE; 77 | hints.ai_protocol = IPPROTO_TCP; 78 | 79 | struct addrinfo *res; 80 | int s = EAI_AGAIN; 81 | int count = 0; 82 | while (EAI_AGAIN==s and count++<20) 83 | { 84 | // connecting to a server, so we can wait until it's ready 85 | s = getaddrinfo(m_address.c_str(), m_port.c_str(), &hints, &res); 86 | if (s && s != EAI_AGAIN) 87 | { 88 | fprintf(stderr, "ERROR: getaddrinfo of %s: %s\n", m_address.c_str(), gai_strerror(s)); 89 | return true; 90 | } 91 | std::this_thread::sleep_for(std::chrono::seconds(3)); 92 | } 93 | 94 | if (EAI_AGAIN == s) 95 | { 96 | fprintf(stderr, "ERROR getaddrinfo of %s failed 20 times\n", m_address.c_str()); 97 | return true; 98 | } 99 | 100 | struct addrinfo *rp; 101 | for (rp = res; rp != NULL; rp = rp->ai_next) 102 | { 103 | m_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 104 | if (m_fd == -1) 105 | continue; 106 | 107 | if (connect(m_fd, rp->ai_addr, rp->ai_addrlen)) 108 | { 109 | Close(); 110 | continue; 111 | } 112 | else 113 | { 114 | char buf[INET6_ADDRSTRLEN]; 115 | void *addr; 116 | if (AF_INET == rp->ai_family) 117 | { 118 | struct sockaddr_in *addr4 = (struct sockaddr_in *)rp->ai_addr; 119 | addr = &(addr4->sin_addr); 120 | } 121 | else 122 | { 123 | struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)rp->ai_addr; 124 | addr = &(addr6->sin6_addr); 125 | } 126 | if (inet_ntop(rp->ai_family, addr, buf, INET6_ADDRSTRLEN)) 127 | fprintf(stderr, "Successfully connected to %s at [%s]:%s\n", m_address.c_str(), buf, m_port.c_str()); 128 | break; 129 | } 130 | } 131 | freeaddrinfo(res); 132 | 133 | if (rp == NULL) 134 | { 135 | fprintf(stderr, "Could not connect to any system returned by %s\n", m_address.c_str()); 136 | m_fd = -1; 137 | return true; 138 | } 139 | 140 | return false; 141 | } 142 | 143 | int CTCPReaderWriterClient::ReadExact(unsigned char *buf, const unsigned int length) 144 | { 145 | unsigned int offset = 0U; 146 | 147 | do 148 | { 149 | int n = Read(buf + offset, length - offset); 150 | if (n < 0) 151 | return n; 152 | 153 | offset += n; 154 | } 155 | while ((length - offset) > 0U); 156 | 157 | return length; 158 | } 159 | 160 | int CTCPReaderWriterClient::Read(unsigned char* buffer, const unsigned int length) 161 | { 162 | assert(buffer != NULL); 163 | assert(length > 0U); 164 | assert(m_fd != -1); 165 | 166 | ssize_t len = recv(m_fd, buffer, length, 0); 167 | if (len <= 0) 168 | { 169 | if (len < 0) 170 | fprintf(stderr, "Error returned from recv, err=%d\n", errno); 171 | return -1; 172 | } 173 | 174 | return len; 175 | } 176 | 177 | int CTCPReaderWriterClient::ReadLine(std::string& line) 178 | { 179 | unsigned char c; 180 | int resultCode; 181 | int len = 0; 182 | line = ""; 183 | 184 | do 185 | { 186 | resultCode = Read(&c, 1); 187 | if(resultCode == 1) 188 | { 189 | line += c; 190 | len++; 191 | } 192 | } 193 | while(c != '\n' && resultCode == 1); 194 | 195 | return resultCode <= 0 ? resultCode : len; 196 | } 197 | 198 | bool CTCPReaderWriterClient::Write(const unsigned char *buffer, const unsigned int length) 199 | { 200 | assert(buffer != NULL); 201 | assert(length > 0U); 202 | assert(m_fd != -1); 203 | 204 | ssize_t ret = send(m_fd, (char *)buffer, length, 0); 205 | if (ret != ssize_t(length)) 206 | { 207 | if (ret < 0) 208 | fprintf(stderr, "Error returned from send, err=%s\n", strerror(errno)); 209 | else 210 | fprintf(stderr, "Error only wrote %d of %d bytes\n", int(ret), int(length)); 211 | return true; 212 | } 213 | 214 | return false; 215 | } 216 | 217 | bool CTCPReaderWriterClient::WriteLine(const std::string& line) 218 | { 219 | std::string lineCopy(line); 220 | if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n') 221 | lineCopy.append("\n"); 222 | 223 | size_t len = lineCopy.size(); 224 | bool result = true; 225 | for(size_t i = 0; i < len && result; i++) 226 | { 227 | unsigned char c = lineCopy.at(i); 228 | result = Write(&c, 1); 229 | } 230 | 231 | return result; 232 | } 233 | 234 | void CTCPReaderWriterClient::Close() 235 | { 236 | if (m_fd != -1) 237 | { 238 | close(m_fd); 239 | m_fd = -1; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /TCPReaderWriterClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* end of inma once */ 4 | /* 5 | * Copyright (C) 2010,2011,2012,2013 by Jonathan Naylor G4KLX 6 | * Copyright (C) 2019 by Thomas A. Early N7TAE 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | class CTCPReaderWriterClient 37 | { 38 | public: 39 | CTCPReaderWriterClient(const std::string &address, int family, const std::string &port); 40 | CTCPReaderWriterClient(); 41 | ~CTCPReaderWriterClient(); 42 | 43 | bool Open(const std::string &address, int family, const std::string &port); 44 | bool Open(); 45 | 46 | int ReadExact(unsigned char *buffer, const unsigned int length); 47 | int Read(unsigned char *buffer, const unsigned int length); 48 | int ReadLine(std::string &line); 49 | bool Write(const unsigned char* buffer, const unsigned int length); 50 | bool WriteLine(const std::string &line); 51 | int GetFD() { return m_fd; } 52 | 53 | void Close(); 54 | 55 | private: 56 | std::string m_address; 57 | int m_family; 58 | std::string m_port; 59 | int m_fd; 60 | }; 61 | -------------------------------------------------------------------------------- /Timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 by Thomas A. Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | class CTimer 25 | { 26 | public: 27 | CTimer() { start(); } 28 | ~CTimer() {} 29 | void start() 30 | { 31 | starttime = std::chrono::steady_clock::now(); 32 | } 33 | double time() 34 | { 35 | std::chrono::duration elapsed(std::chrono::steady_clock::now() - starttime); 36 | return elapsed.count(); 37 | } 38 | private: 39 | std::chrono::steady_clock::time_point starttime; 40 | }; 41 | -------------------------------------------------------------------------------- /UDPSocket.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Thomas A. Early, N7TAE 3 | // 4 | // ---------------------------------------------------------------------------- 5 | // 6 | // xlxd is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // xlxd is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with Foobar. If not, see . 18 | // ---------------------------------------------------------------------------- 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "UDPSocket.h" 30 | 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////// 33 | // constructor 34 | 35 | CUDPSocket::CUDPSocket() : m_fd(-1) {} 36 | 37 | //////////////////////////////////////////////////////////////////////////////////////// 38 | // destructor 39 | 40 | CUDPSocket::~CUDPSocket() 41 | { 42 | Close(); 43 | } 44 | 45 | //////////////////////////////////////////////////////////////////////////////////////// 46 | // open & close 47 | 48 | bool CUDPSocket::Open(const CSockAddress &addr) 49 | { 50 | // create socket 51 | m_fd = socket(addr.GetFamily(), SOCK_DGRAM, 0); 52 | if (0 > m_fd) 53 | { 54 | std::cerr << "Cannot create socket on " << addr << ", " << strerror(errno) << std::endl; 55 | return true; 56 | } 57 | 58 | if (0 > fcntl(m_fd, F_SETFL, O_NONBLOCK)) 59 | { 60 | std::cerr << "cannot set socket " << addr << " to non-blocking: " << strerror(errno) << std::endl; 61 | close(m_fd); 62 | m_fd = -1; 63 | return true; 64 | } 65 | 66 | const int reuse = 1; 67 | if (0 > setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))) 68 | { 69 | std::cerr << "Cannot set the UDP socket option on " << m_addr << ", err: " << strerror(errno) << std::endl; 70 | close(m_fd); 71 | m_fd = -1; 72 | return true; 73 | } 74 | 75 | // initialize sockaddr struct 76 | m_addr = addr; 77 | 78 | if (0 != bind(m_fd, m_addr.GetCPointer(), m_addr.GetSize())) 79 | { 80 | std::cerr << "bind failed on " << m_addr << ", " << strerror(errno) << std::endl; 81 | close(m_fd); 82 | m_fd = -1; 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | void CUDPSocket::Close(void) 90 | { 91 | if ( m_fd >= 0 ) 92 | { 93 | close(m_fd); 94 | m_fd = -1; 95 | } 96 | } 97 | 98 | //////////////////////////////////////////////////////////////////////////////////////// 99 | // read 100 | 101 | size_t CUDPSocket::Read(unsigned char *buf, const size_t size, CSockAddress &Ip) 102 | { 103 | if ( 0 > m_fd ) 104 | return 0; 105 | 106 | unsigned int len = sizeof(struct sockaddr_storage); 107 | auto rval = recvfrom(m_fd, buf, size, 0, Ip.GetPointer(), &len); 108 | if (0 > rval) 109 | std::cerr << "Read error on port " << m_addr << ": " << strerror(errno) << std::endl; 110 | 111 | return rval; 112 | } 113 | 114 | void CUDPSocket::Write(const void *Buffer, const size_t size, const CSockAddress &Ip) const 115 | { 116 | auto rval = sendto(m_fd, Buffer, size, 0, Ip.GetCPointer(), Ip.GetSize()); 117 | if (0 > rval) 118 | std::cerr << "Write error to " << Ip << ", " << strerror(errno) << std::endl; 119 | else if ((size_t)rval != size) 120 | std::cerr << "Short write, " << rval << "<" << size << " to " << Ip << std::endl; 121 | } 122 | -------------------------------------------------------------------------------- /UDPSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // 3 | // Copyright © 2020 Thomas A. Early, N7TAE 4 | // 5 | // ---------------------------------------------------------------------------- 6 | // 7 | // xlxd is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // xlxd is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with Foobar. If not, see . 19 | // ---------------------------------------------------------------------------- 20 | 21 | #include "SockAddress.h" 22 | 23 | #define UDP_BUFFER_LENMAX 1024 24 | 25 | class CUDPSocket 26 | { 27 | public: 28 | CUDPSocket(); 29 | 30 | ~CUDPSocket(); 31 | 32 | bool Open(const CSockAddress &addr); 33 | void Close(void); 34 | 35 | int GetSocket(void) const { return m_fd; } 36 | unsigned short GetPort() const { return m_addr.GetPort(); } 37 | 38 | size_t Read(unsigned char *buf, const size_t size, CSockAddress &addr); 39 | void Write(const void *buf, const size_t size, const CSockAddress &addr) const; 40 | 41 | protected: 42 | int m_fd; 43 | CSockAddress m_addr; 44 | }; 45 | -------------------------------------------------------------------------------- /UnixDgramSocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 by Thomas Early N7TAE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "UnixDgramSocket.h" 31 | 32 | CUnixDgramReader::CUnixDgramReader() : fd(-1) {} 33 | 34 | CUnixDgramReader::~CUnixDgramReader() 35 | { 36 | Close(); 37 | } 38 | 39 | bool CUnixDgramReader::Open(const char *path) // returns true on failure 40 | { 41 | fd = socket(AF_UNIX, SOCK_DGRAM, 0); 42 | if (fd < 0) 43 | { 44 | fprintf(stderr, "CUnixDgramReader::Open: socket() failed: %s\n", strerror(errno)); 45 | return true; 46 | } 47 | //fcntl(fd, F_SETFL, O_NONBLOCK); 48 | 49 | struct sockaddr_un addr; 50 | memset(&addr, 0, sizeof(addr)); 51 | addr.sun_family = AF_UNIX; 52 | strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); 53 | 54 | int rval = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); 55 | if (rval < 0) 56 | { 57 | fprintf(stderr, "CUnixDgramReader::Open: bind() failed: %s\n", strerror(errno)); 58 | close(fd); 59 | fd = -1; 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | ssize_t CUnixDgramReader::Read(void *buf, size_t size) 66 | { 67 | if (fd < 0) 68 | return -1; 69 | ssize_t len = read(fd, buf, size); 70 | if (len < 1) 71 | fprintf(stderr, "CUnixDgramReader::Read read() returned %d: %s\n", int(len), strerror(errno)); 72 | return len; 73 | } 74 | 75 | void CUnixDgramReader::Close() 76 | { 77 | if (fd >= 0) 78 | close(fd); 79 | fd = -1; 80 | } 81 | 82 | int CUnixDgramReader::GetFD() 83 | { 84 | return fd; 85 | } 86 | 87 | CUnixDgramWriter::CUnixDgramWriter() {} 88 | 89 | CUnixDgramWriter::~CUnixDgramWriter() {} 90 | 91 | void CUnixDgramWriter::SetUp(const char *path) // returns true on failure 92 | { 93 | // setup the socket address 94 | memset(&addr, 0, sizeof(addr)); 95 | addr.sun_family = AF_UNIX; 96 | strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); 97 | } 98 | 99 | bool CUnixDgramWriter::Write(const void *buf, size_t size) 100 | { 101 | // open the socket 102 | int fd = socket(AF_UNIX, SOCK_DGRAM, 0); 103 | if (fd < 0) 104 | { 105 | fprintf(stderr, "Failed to open socket %s : %s\n", addr.sun_path+1, strerror(errno)); 106 | return true; 107 | } 108 | // connect to the receiver 109 | int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); 110 | if (rval < 0) 111 | { 112 | fprintf(stderr, "Failed to connect to socket %s : %s\n", addr.sun_path+1, strerror(errno)); 113 | close(fd); 114 | return true; 115 | } 116 | 117 | bool retval = false; 118 | auto written = write(fd, buf, size); 119 | if (written != (ssize_t)size) 120 | { 121 | if (written < 0) 122 | fprintf(stderr, "ERROR: faied to write to %s : %s\n", addr.sun_path+1, strerror(errno)); 123 | else if (written == 0) 124 | fprintf(stderr, "ERROR: zero bytes written to %s\n", addr.sun_path+1); 125 | else 126 | fprintf(stderr, "ERROR: only %d of %d bytes written to %s\n", (int)written, (int)size, addr.sun_path+1); 127 | retval = true; 128 | } 129 | 130 | close(fd); 131 | return retval; 132 | } 133 | -------------------------------------------------------------------------------- /UnixDgramSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright (C) 2019 by Thomas Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | class CUnixDgramReader 24 | { 25 | public: 26 | CUnixDgramReader(); 27 | ~CUnixDgramReader(); 28 | bool Open(const char *path); 29 | ssize_t Read(void *buf, size_t size); 30 | void Close(); 31 | int GetFD(); 32 | private: 33 | int fd; 34 | }; 35 | 36 | class CUnixDgramWriter 37 | { 38 | public: 39 | CUnixDgramWriter(); 40 | ~CUnixDgramWriter(); 41 | void SetUp(const char *path); 42 | bool Write(const void *buf, size_t size); 43 | private: 44 | struct sockaddr_un addr; 45 | }; 46 | -------------------------------------------------------------------------------- /Utilities.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // trim from start (in place) 6 | static inline void ltrim(std::string &s) 7 | { 8 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); 9 | } 10 | 11 | // trim from end (in place) 12 | static inline void rtrim(std::string &s) 13 | { 14 | s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); 15 | } 16 | 17 | // trim from both ends (in place) 18 | static inline void trim(std::string &s) 19 | { 20 | ltrim(s); 21 | rtrim(s); 22 | } 23 | 24 | // trim from start (copying) 25 | static inline std::string ltrim_copy(std::string s) 26 | { 27 | ltrim(s); 28 | return s; 29 | } 30 | 31 | // trim from end (copying) 32 | static inline std::string rtrim_copy(std::string s) 33 | { 34 | rtrim(s); 35 | return s; 36 | } 37 | 38 | // trim from both ends (copying) 39 | static inline std::string trim_copy(std::string s) 40 | { 41 | trim(s); 42 | return s; 43 | } 44 | -------------------------------------------------------------------------------- /announce/already_linked.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/already_linked.dat -------------------------------------------------------------------------------- /announce/already_unlinked.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/already_unlinked.dat -------------------------------------------------------------------------------- /announce/baddtmfcmd.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/baddtmfcmd.dat -------------------------------------------------------------------------------- /announce/connected2network.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/connected2network.dat -------------------------------------------------------------------------------- /announce/failed_link.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/failed_link.dat -------------------------------------------------------------------------------- /announce/gatewaynotfound.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/gatewaynotfound.dat -------------------------------------------------------------------------------- /announce/gatewayrestart.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/gatewayrestart.dat -------------------------------------------------------------------------------- /announce/id.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/id.dat -------------------------------------------------------------------------------- /announce/index.dat: -------------------------------------------------------------------------------- 1 | A 0 29 2 | B 30 32 3 | C 63 34 4 | D 98 32 5 | E 131 26 6 | F 158 31 7 | G 190 36 8 | H 227 31 9 | I 259 28 10 | J 288 36 11 | K 325 28 12 | L 354 28 13 | M 383 34 14 | N 418 32 15 | O 451 29 16 | P 481 32 17 | Q 514 34 18 | R 549 29 19 | S 579 33 20 | T 613 28 21 | U 642 24 22 | V 667 44 23 | W 712 40 24 | X 753 33 25 | Y 787 31 26 | Z 819 36 27 | alpha 856 38 28 | bravo 895 38 29 | charlie 934 37 30 | delta 972 37 31 | echo 1010 33 32 | foxtrot 1044 56 33 | golf 1101 38 34 | hotel 1140 39 35 | india 1180 36 36 | juliette 1217 39 37 | kilo 1257 33 38 | lima 1291 41 39 | mike 1333 33 40 | november 1367 38 41 | oscar 1406 40 42 | papa 1447 35 43 | quebec 1483 36 44 | romeo 1520 39 45 | sierra 1560 35 46 | tango 1596 40 47 | uniform 1637 45 48 | victor 1683 34 49 | whiskey 1718 33 50 | X-ray 1752 40 51 | yankee 1793 39 52 | zulu 1833 38 53 | 1 1872 34 54 | 2 1907 28 55 | 3 1936 37 56 | 4 1974 35 57 | 5 2010 37 58 | 6 2048 35 59 | 7 2084 38 60 | 8 2123 28 61 | 9 2152 37 62 | 0 2190 33 63 | 64 | -------------------------------------------------------------------------------- /announce/linked.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/linked.dat -------------------------------------------------------------------------------- /announce/notincache.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/notincache.dat -------------------------------------------------------------------------------- /announce/rebooting.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/rebooting.dat -------------------------------------------------------------------------------- /announce/shutdown.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/shutdown.dat -------------------------------------------------------------------------------- /announce/speak.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/speak.dat -------------------------------------------------------------------------------- /announce/unlinked.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n7tae/QnetGateway/4d8cc8bd9de62a50d4e3b0583319f19d451bf5e6/announce/unlinked.dat -------------------------------------------------------------------------------- /aprs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2016 by Thomas A. Early N7TAE 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "TCPReaderWriterClient.h" 27 | #include "IRCutils.h" 28 | 29 | enum aprs_level { al_none, al_$1, al_$2, al_c1, al_r1, al_c2, al_csum1, al_csum2, al_csum3, al_csum4, al_data, al_end }; 30 | 31 | enum slow_level { sl_first, sl_second }; 32 | 33 | using SPORTIP = struct portip_tag 34 | { 35 | std::string ip; 36 | int port; 37 | }; 38 | 39 | using SMOD = struct aprs_module 40 | { 41 | std::string call; /* KJ4NHF-B */ 42 | bool defined; 43 | std::string band; /* 23cm ... */ 44 | double frequency, offset, latitude, longitude, range, agl; 45 | std::string desc1, desc2, url, package_version; 46 | }; 47 | 48 | using SRPTR = struct aprs_info 49 | { 50 | SPORTIP aprs; 51 | std::string aprs_filter; 52 | int aprs_hash; 53 | int aprs_interval; 54 | 55 | /* 0=A, 1=B, 2=C */ 56 | SMOD mod[3]; 57 | }; 58 | 59 | class CAPRS 60 | { 61 | public: 62 | // functions 63 | CAPRS(SRPTR *prptr); 64 | ~CAPRS(); 65 | SRPTR *m_rptr; 66 | void SelectBand(short int rptr_idx, unsigned short streamID); 67 | void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf); 68 | void Open(const std::string OWNER); 69 | void Init(); 70 | void CloseSock(); 71 | CTCPReaderWriterClient aprs_sock; 72 | 73 | private: 74 | // data 75 | struct 76 | { 77 | aprs_level al; 78 | unsigned char data[300]; 79 | unsigned int len; 80 | unsigned char buf[6]; 81 | slow_level sl; 82 | bool is_sent; 83 | } aprs_pack[3]; 84 | // lock down a stream per band 85 | struct 86 | { 87 | unsigned short streamID; 88 | time_t last_time; 89 | } aprs_streamID[3]; 90 | 91 | // functions 92 | bool WriteData(short int rptr_idx, unsigned char *data); 93 | void SyncIt(short int rptr_idx); 94 | void Reset(short int rptr_idx); 95 | unsigned int GetData(short int rptr_idx, unsigned char *data, unsigned int len); 96 | bool AddData(short int rptr_idx, unsigned char *data); 97 | bool CheckData(short int rptr_idx); 98 | unsigned int CalcCRC(unsigned char* buf, unsigned int len); 99 | }; 100 | -------------------------------------------------------------------------------- /bash_aliases: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 by Thomas A. Early N7TAE 2 | 3 | # copy this to ~/.bash_aliases if you don't want to use qnadmin to start and stop QnetGateway 4 | 5 | function start () { 6 | if [ $# == 1 ]; then 7 | sudo make installbase && sudo make install${1} && sudo journalctl -u qn${1} -f 8 | elif [ $# == 2 ]; then 9 | sudo make installbase && sudo make install${1} && sudo journalctl -u qn${2} -f 10 | else 11 | echo "Usage: start module_name [watch_module]" 12 | echo "Installs the base system and the module_name prefixed with 'qn' and tails the log." 13 | echo "Use watch_module if you want to tail a different log" 14 | echo "Only use this alias for systems with a single defined module." 15 | echo "You must be in the QnetGateway build directory" 16 | fi 17 | } 18 | 19 | function stop () { 20 | if [ $# == 1 ]; then 21 | sudo make uninstallbase && sudo make uninstall${1} 22 | else 23 | echo "usage: stop module_name" 24 | echo "Uninstalls the base system and the module_name prefixed with 'qn'." 25 | echo "Use this alias on for systems with a single defined module." 26 | fi 27 | } 28 | 29 | function watch () { 30 | if [ $# == 1 ]; then 31 | sudo journalctl -u qn${1} -f 32 | else 33 | echo "usage: watch service_name" 34 | echo "Tails the log from the service_name prefixed with 'qn'." 35 | fi 36 | } 37 | -------------------------------------------------------------------------------- /dashboardV2/bin/getJson.php: -------------------------------------------------------------------------------- 1 | 8 ) 12 | { 13 | # OpenDatabase 14 | $dbname = $cfgdir.'/qn.db'; 15 | $db = new SQLite3($dbname, SQLITE3_OPEN_READONLY); 16 | 17 | # Only proccess if defined in show list 18 | if( in_array("LH", $showlist) ) { 19 | $jsonArray = []; 20 | $ss = 'SELECT callsign,sfx,message,module,reflector,maidenhead,latitude,longitude,strftime("%s","now")-lasttime as lastTime FROM LHEARD ORDER BY lastTime LIMIT '.GetCFGValue('dash_lastheard_count').' '; 21 | if ($stmnt = $db->prepare($ss)) { 22 | if ($result = $stmnt->execute()) { 23 | while ($row = $result->FetchArray(SQLITE3_ASSOC)) { 24 | //transform the lastTimeHeard to a printable string 25 | $row['lastTime'] = SecToString($row['lastTime']); 26 | $row['maidenheadProcessed'] = Maidenhead($row['maidenhead'], $row['latitude'], $row['longitude']); 27 | $row['callsignProcessed'] = MyAndSfxToQrz($row['callsign'], $row['sfx']); 28 | $jsonArray[] = $row; 29 | } 30 | $result->finalize(); 31 | } 32 | $stmnt->close(); 33 | } 34 | 35 | # Write the lastHeard JSON file 36 | $lhJsonFile = fopen("../jsonData/lastHeard.json", "w"); 37 | fwrite($lhJsonFile, json_encode($jsonArray)); 38 | fclose($lhJsonFile); 39 | } else { echo "Section disabled"; 40 | $lhJsonFile = fopen("../jsonData/lastHeard.json", "w"); 41 | fwrite($lhJsonFile, "{ }\n"); 42 | fclose($lhJsonFile); 43 | } 44 | 45 | 46 | # Only proccess if defined in show list 47 | if( in_array("MO", $showlist) ) { 48 | $jsonArray = []; 49 | foreach (array('a', 'b', 'c') as $mod) { 50 | $linkstatus = 'Unlinked'; 51 | $address = ''; 52 | $ctime = ''; 53 | $module = 'module_'.$mod; 54 | if (array_key_exists($module, $cfg)) { 55 | $freq = 0.0; 56 | if (array_key_exists($module.'_tx_frequency', $cfg)) { 57 | $freq = $cfg[$module.'_tx_frequency']; 58 | } 59 | else if (array_key_exists($module.'_frequency', $cfg)) { 60 | $freq = $cfg[$module.'_frequency']; 61 | } 62 | $ss = 'SELECT ip_address,to_callsign,to_mod,strftime("%s","now")-linked_time as linkedTime FROM LINKSTATUS WHERE from_mod=' . "'" . strtoupper($mod) . "';"; 63 | if ($stmnt = $db->prepare($ss)) { 64 | if ($result = $stmnt->execute()) { 65 | if ($row = $result->FetchArray(SQLITE3_ASSOC)) { 66 | $row['linkedTime'] = SecToString(intval($row['linkedTime'])); 67 | $row['module'] = strtoupper($mod); 68 | $row['modem'] = $cfg[$module]; 69 | $row['freq'] = $freq; 70 | $row['link'] = $row['to_callsign']." ".$row['to_mod']; 71 | $jsonArray[] = $row; 72 | } else { 73 | $jsonArray[] = array('linkedTime' => '', 74 | 'module' =>strtoupper($mod), 75 | 'modem' => $cfg[$module], 76 | 'freq' => $freq, 77 | 'link' => 'Unlinked', 78 | 'ip_address' => '', 79 | 'to_callsign' => '', 80 | 'to_mod' => ''); 81 | } 82 | $result->finalize(); 83 | } 84 | $stmnt->close(); 85 | } 86 | } 87 | } 88 | $modJsonFile = fopen("../jsonData/modules.json", "w"); 89 | fwrite($modJsonFile, json_encode($jsonArray)); 90 | fclose($modJsonFile); 91 | } else { 92 | $modJsonFile = fopen("../jsonData/modules.json", "w"); 93 | fwrite($modJsonFile, "{ }\n"); 94 | fclose($modJsonFile); 95 | } 96 | 97 | # Close database it is not needed anymore 98 | $db->Close(); 99 | 100 | 101 | # Only proccess if defined in show list 102 | if( in_array("PS", $showlist) ) { 103 | $jsonArray = []; 104 | $lines = explode("\n", `ps -eo user,pid,pcpu,size,cmd | grep -e qngateway -e qnlink -e qndtmf -e qndvap -e qnitap -e qnrelay -e qndvrptr -e qnmodem -e MMDVMHost | grep -v grep`); 105 | foreach ($lines as $line) { 106 | $items = preg_split ('/\s+/', $line, 5); 107 | if( isset( $items[1] ) ) { 108 | $jsonArray[] = array('user' => $items[0], 109 | 'pid' => $items[1], 110 | 'pcpu' => $items[2], 111 | 'size' => $items[3], 112 | 'cmd' => $items[4]); 113 | } 114 | } 115 | $psJsonFile = fopen("../jsonData/ps.json", "w"); 116 | if ($jsonArray) { 117 | fwrite($psJsonFile, json_encode($jsonArray)); 118 | } else { 119 | fwrite($psJsonFile, "{ }\n"); 120 | } 121 | fclose($psJsonFile); 122 | } else { 123 | # Section is disabled, replace with blank JSON file 124 | $psJsonFile = fopen("../jsonData/ps.json", "w"); 125 | fwrite($psJsonFile, "{ }\n"); 126 | fclose($psJsonFile); 127 | } 128 | 129 | # Update last run time 130 | `touch $lastRunFile`; 131 | } 132 | 133 | # If the jsonFile is in the URL lets get the file 134 | if( isset($_GET['jsonFile']) ) 135 | { 136 | if( $_GET['jsonFile'] == "lastHeard" ) 137 | readfile("../jsonData/lastHeard.json"); 138 | else if( $_GET['jsonFile'] == "modules" ) 139 | readfile("../jsonData/modules.json"); 140 | else if( $_GET['jsonFile'] == "ps" ) 141 | readfile("../jsonData/ps.json"); 142 | } 143 | ?> 144 | 145 | -------------------------------------------------------------------------------- /dashboardV2/bin/qnRemoteCmd.php: -------------------------------------------------------------------------------- 1 | 2 | 0 && strlen($_POST['mod'])>0) { 12 | $command = 'qnremote '.strtolower($_POST['mod']).' '.strtolower($cfg['ircddb_login']).' '.$urcall; 13 | $unused = `$command`; 14 | 15 | # Return the command sent for the front-end display 16 | echo $command; 17 | } 18 | } 19 | } else { 20 | echo "Section disabled"; 21 | } 22 | ?> 23 | -------------------------------------------------------------------------------- /dashboardV2/css/bootstrap-table.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) 3 | * 4 | * @version v1.16.0 5 | * @homepage https://bootstrap-table.com 6 | * @author wenzhixin (http://wenzhixin.net.cn/) 7 | * @license MIT 8 | */ 9 | 10 | .bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:none;justify-content:center;position:absolute;bottom:0;width:100%;z-index:1000}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{font-size:2rem;margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:LOADING;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination a{padding:6px 12px;line-height:1.428571429}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:'\2B05'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:'\27A1'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes LOADING{0%{opacity:0}50%{opacity:1}to{opacity:0}} -------------------------------------------------------------------------------- /dashboardV2/init.php: -------------------------------------------------------------------------------- 1 | = 86400) 62 | return sprintf("%0.2f days", $sec/86400); 63 | $hrs = intdiv($sec, 3600); 64 | $sec %= 3600; 65 | $min = intdiv($sec, 60); 66 | $sec %= 60; 67 | if ($hrs) return sprintf("%2d hr %2d min", $hrs, $min); 68 | if ($min) return sprintf("%2d min %2d sec", $min, $sec); 69 | return sprintf("%2d sec", $sec); 70 | } 71 | 72 | function MyAndSfxToQrz(string $my, string $sfx) 73 | { 74 | $my = trim($my); 75 | $sfx = trim($sfx); 76 | if (0 == strlen($my)) { 77 | $my = 'Empty MYCall '; 78 | } else { 79 | if (strpos($my, ' ')) 80 | $link = strstr($my, ' ', true); 81 | else 82 | $link = $my; 83 | if (strlen($sfx)) 84 | $my .= '/'.$sfx; 85 | $len = strlen($my); 86 | $my = ''.$my.''; 87 | while ($len < 13) { 88 | $my .= ' '; 89 | $len += 1; 90 | } 91 | } 92 | return $my; 93 | } 94 | 95 | function Maidenhead(string $maid, float $lat, float $lon) 96 | { 97 | $str = trim($maid); 98 | if (6 > strlen($str)) 99 | return $maid; 100 | if ($lat >= 0.0) 101 | $slat = '+'.$lat; 102 | else 103 | $slat = $lat; 104 | if ($lon >= 0.0) 105 | $slon = '+'.$lon; 106 | else 107 | $slon = $lon; 108 | $str = ''.$maid.''; 109 | return $str; 110 | } 111 | 112 | 113 | ParseKVFile($cfgdir.'/qn.cfg', $cfg); 114 | ParseKVFile($cfgdir.'/defaults', $defaults); 115 | 116 | $showorder = GetCFGValue('dash_show_order'); 117 | $showlist = explode(',', trim($showorder)); 118 | 119 | ?> 120 | -------------------------------------------------------------------------------- /dashboardV2/jsonData/README: -------------------------------------------------------------------------------- 1 | This folder is for temporary data used by dashboardV2 2 | -------------------------------------------------------------------------------- /exec_G.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | qnvoice ${2} gatewayrestart.dat 'Gateway Restart' 3 | sleep 5 4 | systemctl restart qngateway 5 | -------------------------------------------------------------------------------- /exec_H.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | qnvoice ${2} shutdown.dat 'System Shutdown' 3 | sleep 5 4 | shutdown -h now 5 | -------------------------------------------------------------------------------- /exec_R.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | qnvoice ${2} rebooting.dat 'System Reboot' 3 | sleep 5 4 | shutdown -r now 5 | -------------------------------------------------------------------------------- /ircddb/IRCClient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "IRCClient.h" 8 | #include "IRCutils.h" 9 | #include "IRCDDBApp.h" 10 | 11 | IRCClient::IRCClient(IRCDDBApp *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo) 12 | { 13 | safeStringCopy(host_name, hostName.c_str(), sizeof host_name); 14 | 15 | 16 | this->callsign = callsign; 17 | ToLower(this->callsign); 18 | this->port = port; 19 | this->password = password; 20 | 21 | this->app = app; 22 | 23 | proto.Init(app, this->callsign, password, update_channel, versionInfo); 24 | 25 | recvQ = NULL; 26 | sendQ = NULL; 27 | } 28 | 29 | IRCClient::~IRCClient() 30 | { 31 | } 32 | 33 | bool IRCClient::startWork() 34 | { 35 | 36 | terminateThread = false; 37 | client_thread = std::async(std::launch::async, &IRCClient::Entry, this); 38 | return true; 39 | } 40 | 41 | void IRCClient::stopWork() 42 | { 43 | terminateThread = true; 44 | client_thread.get(); 45 | } 46 | 47 | #define MAXIPV4ADDR 10 48 | void IRCClient::Entry() 49 | { 50 | CTCPReaderWriterClient ircSock; 51 | 52 | int state = 0; 53 | int timer = 0; 54 | socklen_t optlen; 55 | 56 | while (true) 57 | { 58 | 59 | if (timer > 0) 60 | { 61 | timer--; 62 | } 63 | 64 | switch (state) 65 | { 66 | case 0: 67 | if (terminateThread) 68 | { 69 | printf("IRCClient::Entry: thread terminated at state=%d\n", state); 70 | return; 71 | } 72 | 73 | if (timer == 0) 74 | { 75 | timer = 30; 76 | 77 | if (! ircSock.Open(host_name, AF_UNSPEC, std::to_string(port))) 78 | { 79 | state = 4; 80 | timer = 0; 81 | } 82 | } 83 | break; 84 | 85 | 86 | case 4: 87 | optlen = sizeof(int); 88 | getsockopt(ircSock.GetFD(), SOL_SOCKET, SO_DOMAIN, &family, &optlen); 89 | recvQ = new IRCMessageQueue(); 90 | sendQ = new IRCMessageQueue(); 91 | 92 | receiver.Init(&ircSock, recvQ); 93 | receiver.startWork(); 94 | 95 | proto.setNetworkReady(true); 96 | state = 5; 97 | timer = 0; 98 | break; 99 | 100 | 101 | case 5: 102 | if (terminateThread) 103 | { 104 | state = 6; 105 | } 106 | else 107 | { 108 | 109 | if (recvQ->isEOF()) 110 | { 111 | timer = 0; 112 | state = 6; 113 | } 114 | else if (proto.processQueues(recvQ, sendQ) == false) 115 | { 116 | timer = 0; 117 | state = 6; 118 | } 119 | 120 | while ((state == 5) && sendQ->messageAvailable()) 121 | { 122 | IRCMessage * m = sendQ->getMessage(); 123 | 124 | std::string out; 125 | 126 | m->composeMessage(out); 127 | 128 | char buf[200]; 129 | safeStringCopy(buf, out.c_str(), sizeof buf); 130 | int len = strlen(buf); 131 | 132 | if (buf[len - 1] == 10) // is there a NL char at the end? 133 | { 134 | if (ircSock.Write((unsigned char *)buf, len)) 135 | { 136 | printf("IRCClient::Entry: short write\n"); 137 | 138 | timer = 0; 139 | state = 6; 140 | } 141 | } 142 | else 143 | { 144 | printf("IRCClient::Entry: no NL at end, len=%d\n", len); 145 | 146 | timer = 0; 147 | state = 6; 148 | } 149 | 150 | delete m; 151 | } 152 | } 153 | break; 154 | 155 | case 6: 156 | { 157 | if (app != NULL) 158 | { 159 | app->setSendQ(NULL); 160 | app->userListReset(); 161 | } 162 | 163 | proto.setNetworkReady(false); 164 | receiver.stopWork(); 165 | 166 | sleep(2); 167 | 168 | delete recvQ; 169 | delete sendQ; 170 | 171 | ircSock.Close(); 172 | 173 | if (terminateThread) // request to end the thread 174 | { 175 | printf("IRCClient::Entry: thread terminated at state=%d\n", state); 176 | return; 177 | } 178 | 179 | timer = 30; 180 | state = 0; // reconnect to IRC server 181 | } 182 | break; 183 | } // switch 184 | usleep(500000); 185 | 186 | } 187 | return; 188 | } 189 | 190 | int IRCClient::GetFamily() 191 | { 192 | return family; 193 | } 194 | -------------------------------------------------------------------------------- /ircddb/IRCClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../TCPReaderWriterClient.h" 6 | 7 | #include "IRCReceiver.h" 8 | #include "IRCMessageQueue.h" 9 | #include "IRCProtocol.h" 10 | #include "IRCReceiver.h" 11 | 12 | class IRCDDBApp; 13 | 14 | class IRCClient 15 | { 16 | public: 17 | IRCClient(IRCDDBApp *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo); 18 | 19 | virtual ~IRCClient(); 20 | bool startWork(); 21 | void stopWork(); 22 | int GetFamily(); 23 | 24 | protected: 25 | virtual void Entry(); 26 | 27 | private: 28 | std::atomic family; 29 | std::future client_thread; 30 | char host_name[100]; 31 | char local_addr[100]; 32 | unsigned int port; 33 | std::string callsign; 34 | std::string password; 35 | 36 | bool terminateThread; 37 | 38 | IRCReceiver receiver; 39 | IRCMessageQueue *recvQ; 40 | IRCMessageQueue *sendQ; 41 | IRCProtocol proto; 42 | IRCDDBApp *app; 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /ircddb/IRCDDB.cpp: -------------------------------------------------------------------------------- 1 | #include "IRCDDB.h" 2 | 3 | #include "IRCClient.h" 4 | #include "IRCDDBApp.h" 5 | #include "IRCutils.h" 6 | 7 | CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, bool log_irc) 8 | 9 | { 10 | const std::string update_channel("#dstar"); 11 | 12 | app = new IRCDDBApp(update_channel, &cache, log_irc); 13 | client = new IRCClient(app, update_channel, hostName, port, callsign, password, versionInfo); 14 | } 15 | 16 | CIRCDDB::~CIRCDDB() 17 | { 18 | delete client; 19 | delete app; 20 | } 21 | 22 | int CIRCDDB::GetFamily() 23 | { 24 | return client->GetFamily(); 25 | } 26 | 27 | 28 | // A false return implies a network error, or unable to log in 29 | bool CIRCDDB::open() 30 | { 31 | printf("starting CIRCDDB\n"); 32 | return client->startWork() && app->startWork(); 33 | } 34 | 35 | 36 | int CIRCDDB::getConnectionState() 37 | { 38 | return app->getConnectionState(); 39 | } 40 | 41 | 42 | void CIRCDDB::rptrQTH(const std::string &rptrcall, double latitude, double longitude, const std::string &desc1, const std::string &desc2, const std::string &infoURL, const std::string &swVersion) 43 | { 44 | app->rptrQTH(rptrcall, latitude, longitude, desc1, desc2, infoURL, swVersion); 45 | } 46 | 47 | 48 | void CIRCDDB::rptrQRG(const std::string &rptrcall, double txFrequency, double duplexShift, double range, double agl) 49 | { 50 | app->rptrQRG(rptrcall, txFrequency, duplexShift, range, agl); 51 | } 52 | 53 | 54 | void CIRCDDB::kickWatchdog(const std::string &wdInfo) 55 | { 56 | app->kickWatchdog(wdInfo); 57 | } 58 | 59 | // Send heard data, a false return implies a network error 60 | bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3) 61 | { 62 | if (myCall.size() != 8) 63 | { 64 | printf("CIRCDDB::sendHeard:myCall: len != 8\n"); 65 | return false; 66 | } 67 | 68 | if (myCallExt.size() != 4) 69 | { 70 | printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); 71 | return false; 72 | } 73 | 74 | if (yourCall.size() != 8) 75 | { 76 | printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); 77 | return false; 78 | } 79 | 80 | if (rpt1.size() != 8) 81 | { 82 | printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); 83 | return false; 84 | } 85 | 86 | if (rpt2.size() != 8) 87 | { 88 | printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); 89 | return false; 90 | } 91 | 92 | return app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, " ", "", ""); 93 | } 94 | 95 | // Send heard data, a false return implies a network error 96 | bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message) 97 | { 98 | if (myCall.size() != 8) 99 | { 100 | printf("CIRCDDB::sendHeard:myCall: len != 8\n"); 101 | return false; 102 | } 103 | 104 | if (myCallExt.size() != 4) 105 | { 106 | printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); 107 | return false; 108 | } 109 | 110 | if (yourCall.size() != 8) 111 | { 112 | printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); 113 | return false; 114 | } 115 | 116 | if (rpt1.size() != 8) 117 | { 118 | printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); 119 | return false; 120 | } 121 | 122 | if (rpt2.size() != 8) 123 | { 124 | printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); 125 | return false; 126 | } 127 | 128 | std::string dest = network_destination; 129 | 130 | if (dest.size() == 0) 131 | dest = " "; 132 | 133 | if (dest.size() != 8) 134 | { 135 | printf("CIRCDDB::sendHeard:network_destination: len != 8\n"); 136 | return false; 137 | } 138 | 139 | std::string msg; 140 | 141 | if (tx_message.length() == 20) 142 | { 143 | for (unsigned int i=0; i < tx_message.size(); i++) 144 | { 145 | char ch = tx_message.at(i); 146 | 147 | if (ch>32 && ch<127) 148 | { 149 | msg.push_back(ch); 150 | } 151 | else 152 | { 153 | msg.push_back('_'); 154 | } 155 | } 156 | } 157 | 158 | return app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, dest, msg, ""); 159 | } 160 | 161 | bool CIRCDDB::sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, 162 | unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors) 163 | { 164 | if (num_dv_frames<= 0 || num_dv_frames>65535) 165 | { 166 | printf("CIRCDDB::sendHeard:num_dv_frames not in range 1-65535\n"); 167 | return false; 168 | } 169 | 170 | if (num_dv_silent_frames > num_dv_frames) 171 | { 172 | printf("CIRCDDB::sendHeard:num_dv_silent_frames > num_dv_frames\n"); 173 | return false; 174 | } 175 | 176 | if (num_bit_errors > 4*num_dv_frames) // max 4 bit errors per frame 177 | { 178 | printf("CIRCDDB::sendHeard:num_bit_errors > (4*num_dv_frames)\n"); 179 | return false; 180 | } 181 | 182 | if (myCall.size() != 8) 183 | { 184 | printf("CIRCDDB::sendHeard:myCall: len != 8\n"); 185 | return false; 186 | } 187 | 188 | if (myCallExt.size() != 4) 189 | { 190 | printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); 191 | return false; 192 | } 193 | 194 | if (yourCall.size() != 8) 195 | { 196 | printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); 197 | return false; 198 | } 199 | 200 | if (rpt1.size() != 8) 201 | { 202 | printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); 203 | return false; 204 | } 205 | 206 | if (rpt2.size() != 8) 207 | { 208 | printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); 209 | return false; 210 | } 211 | 212 | char buf[16]; 213 | snprintf(buf, 16, "%04x", num_dv_frames); 214 | std::string stats = buf; 215 | 216 | if (num_dv_silent_frames >= 0) 217 | { 218 | snprintf(buf, 16, "%02x", num_dv_silent_frames * 100 / num_dv_frames); 219 | stats.append(buf); 220 | 221 | if (num_bit_errors >= 0) 222 | { 223 | snprintf(buf,16, "%02x", num_bit_errors * 125 / (num_dv_frames * 3)); 224 | stats.append(buf); 225 | } 226 | else 227 | { 228 | stats.append("__"); 229 | } 230 | } 231 | else 232 | { 233 | stats.append("____"); 234 | } 235 | 236 | stats.append("____________"); // stats string should have 20 chars 237 | 238 | return app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, " ", "", stats); 239 | } 240 | 241 | // Send query for a user, a false return implies a network error 242 | bool CIRCDDB::findUser(const std::string &userCallsign) 243 | { 244 | if (userCallsign.size() != 8) 245 | { 246 | printf("CIRCDDB::findUser: len != 8\n"); 247 | return false; 248 | } 249 | std::string ucs = userCallsign; 250 | ToUpper(ucs); 251 | return app->findUser(ucs); 252 | } 253 | 254 | // The following functions are for processing received messages 255 | 256 | // Get the waiting message type 257 | IRCDDB_RESPONSE_TYPE CIRCDDB::getMessageType() 258 | { 259 | return app->getReplyMessageType(); 260 | } 261 | 262 | bool CIRCDDB::receivePing(std::string &repeaterCallsign) 263 | { 264 | IRCDDB_RESPONSE_TYPE rt = app->getReplyMessageType(); 265 | 266 | if (rt != IDRT_PING) 267 | { 268 | printf("CIRCDDB::receivePing: unexpected response type\n"); 269 | return false; 270 | } 271 | 272 | IRCMessage *m = app->getReplyMessage(); 273 | 274 | if (NULL == m) 275 | { 276 | printf("CIRCDDB::receivePing: no message\n"); 277 | return false; 278 | } 279 | 280 | if (m->getCommand().compare("IDRT_PING")) 281 | { 282 | printf("CIRCDDB::receivePing: wrong messsage type\n"); 283 | return false; 284 | } 285 | 286 | if (1 != m->getParamCount()) 287 | { 288 | printf("CIRCDDB::receivePing: unexpected number of message parameters\n"); 289 | return false; 290 | } 291 | 292 | repeaterCallsign = m->getParam(0); 293 | 294 | delete m; 295 | 296 | return true; 297 | } 298 | 299 | void CIRCDDB::sendPing(const std::string &to, const std::string &from) 300 | { 301 | app->sendPing(to, from); 302 | } 303 | 304 | void CIRCDDB::close() // Implictely kills any threads in the IRC code 305 | { 306 | client->stopWork(); 307 | app->stopWork(); 308 | } 309 | -------------------------------------------------------------------------------- /ircddb/IRCDDB.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../CacheManager.h" 6 | 7 | enum IRCDDB_RESPONSE_TYPE 8 | { 9 | IDRT_NONE, 10 | IDRT_PING 11 | }; 12 | 13 | enum DSTAR_PROTOCOL 14 | { 15 | DP_UNKNOWN, 16 | DP_DEXTRA, 17 | DP_DPLUS 18 | }; 19 | 20 | class IRCDDBApp; 21 | class IRCClient; 22 | 23 | class CIRCDDB 24 | { 25 | public: 26 | CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, bool log_irc); 27 | ~CIRCDDB(); 28 | 29 | // returns the socket family type 30 | int GetFamily(); 31 | 32 | // A false return implies a network error, or unable to log in 33 | bool open(); 34 | 35 | // rptrQTH can be called multiple times if necessary 36 | // rptrcall callsign of the repeater 37 | // latitude WGS84 position of antenna in degrees, positive value -> NORTH 38 | // longitude WGS84 position of antenna in degrees, positive value -> EAST 39 | // desc1, desc2 20-character description of QTH 40 | 41 | void rptrQTH(const std::string &rptrcall, double latitude, double longitude, const std::string &desc1, const std::string &desc2, const std::string &infoURL, const std::string &swVersion); 42 | 43 | // rptrQRG can be called multiple times if necessary 44 | // module letter of the module, valid values: "A", "B", "C", "D", "AD", "BD", "CD", "DD" 45 | // txFrequency repeater TX frequency in MHz 46 | // duplexShift duplex shift in MHz (positive or negative value): RX_freq = txFrequency + duplexShift 47 | // range range of the repeater in meters (meters = miles * 1609.344) 48 | // agl height of the antenna above ground in meters (meters = feet * 0.3048) 49 | 50 | void rptrQRG(const std::string &rptrcall, double txFrequency, double duplexShift, double range, double agl); 51 | 52 | // If you call this method once, watchdog messages will be sent to the 53 | // to the ircDDB network every 15 minutes. Invoke this method every 1-2 minutes to indicate 54 | // that the gateway is working properly. After activating the watchdog, a red LED will be displayed 55 | // on the ircDDB web page if this method is not called within a period of about 30 minutes. 56 | // The string wdInfo should contain information about the source of the alive messages, e.g., 57 | // version of the RF decoding software. For example, the ircDDB java software sets this 58 | // to "rpm_ircddbmhd-x.z-z". The string wdInfo must contain at least one non-space character. 59 | 60 | void kickWatchdog(const std::string &wdInfo); 61 | 62 | // get internal network status 63 | int getConnectionState(); 64 | // one of these values is returned: 65 | // 0 = not (yet) connected to the IRC server 66 | // 1-6 = a new connection was established, download of repeater info etc. is 67 | // in progress 68 | // 7 = the ircDDB connection is fully operational 69 | // 10 = some network error occured, next state is "0" (new connection attempt) 70 | 71 | // Send heard data, a false return implies a network error 72 | bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3); 73 | 74 | // same as sendHeard with two new fields: 75 | // network_destination: empty string or 8-char call sign of the repeater 76 | // or reflector, where this transmission is relayed to. 77 | // tx_message: 20-char TX message or empty string, if the user did not 78 | // send a TX message 79 | bool sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message); 80 | 81 | // this method should be called at the end of a transmission 82 | // num_dv_frames: number of DV frames sent out (96 bit frames, 20ms) 83 | // num_dv_silent_frames: number of DV silence frames sent out in the 84 | // last transmission, or -1 if the information is not available 85 | // num_bit_errors: number of bit errors of the received data. This should 86 | // be the derived from the first Golay block of the voice data. This 87 | // error correction code only looks at 24 bits of the 96 bit frame. 88 | // So, the overall bit error rate is calculated like this: 89 | // BER = num_bit_errors / (num_dv_frames * 24) 90 | // Set num_bit_errors = -1, if the error information is not available. 91 | bool sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors); 92 | 93 | // The following three functions don't block waiting for a reply, they just send the data 94 | 95 | // Send query for a user, a false return implies a network error 96 | bool findUser(const std::string &userCallsign); 97 | 98 | // The following functions are for processing received messages 99 | 100 | // Get the waiting message type 101 | IRCDDB_RESPONSE_TYPE getMessageType(); 102 | 103 | // Get a gateway message, as a result of IDRT_REPEATER returned from getMessageType() 104 | // A false return implies a network error 105 | bool receivePing(std::string &repeaterCallsign); 106 | 107 | void sendPing(const std::string &to, const std::string &from); 108 | 109 | void close(); // Implictely kills any threads in the IRC code 110 | 111 | CCacheManager cache; 112 | 113 | private: 114 | IRCDDBApp *app; 115 | IRCClient *client; 116 | }; 117 | -------------------------------------------------------------------------------- /ircddb/IRCDDBApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "IRCDDB.h" 10 | #include "IRCMessageQueue.h" 11 | 12 | class IRCDDBApp 13 | { 14 | public: 15 | IRCDDBApp(const std::string &update_channel, CCacheManager *cache, bool log_irc); 16 | ~IRCDDBApp(); 17 | 18 | void userJoin(const std::string &nick, const std::string &name, const std::string &host); 19 | 20 | void userLeave(const std::string &nick); 21 | 22 | void userListReset(); 23 | 24 | void msgChannel(IRCMessage *m); 25 | void msgQuery(IRCMessage *m); 26 | 27 | void setCurrentNick(const std::string &nick); 28 | 29 | void setBestServer(const std::string &ircUser); 30 | 31 | void setSendQ(IRCMessageQueue *s); 32 | IRCMessageQueue *getSendQ(); 33 | 34 | void putReplyMessage(IRCMessage *m); 35 | void sendPing(const std::string &to, const std::string &from); 36 | 37 | bool startWork(); 38 | void stopWork(); 39 | 40 | IRCDDB_RESPONSE_TYPE getReplyMessageType(); 41 | 42 | IRCMessage *getReplyMessage(); 43 | 44 | bool findUser(const std::string &s); 45 | 46 | bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &destination, const std::string &tx_msg, const std::string &tx_stats); 47 | 48 | int getConnectionState(); 49 | 50 | void rptrQRG(const std::string &rptrcall, double txFrequency, double duplexShift, double range, double agl); 51 | 52 | void rptrQTH(const std::string &rtprcall, double latitude, double longitude, const std::string &desc1, const std::string &desc2, const std::string &infoURL, const std::string &swVersion); 53 | 54 | void kickWatchdog(const std::string &wdInfo); 55 | 56 | protected: 57 | void Entry(); 58 | 59 | private: 60 | const int numberOfTables; 61 | void doUpdate(std::string &msg); 62 | void doNotFound(std::string &msg, std::string &retval); 63 | bool findServerUser(); 64 | std::string getTableIDString(int tableID, bool spaceBeforeNumber); 65 | std::string getLastEntryTime(int tableID); 66 | std::future worker_thread; 67 | IRCMessageQueue *sendQ; 68 | IRCMessageQueue replyQ; 69 | CCacheManager *cache; 70 | 71 | std::map moduleMap; 72 | std::mutex moduleMapMutex; 73 | std::map locationMap; 74 | std::mutex locationMapMutex; 75 | std::map urlMap; 76 | std::mutex urlMapMutex; 77 | std::map swMap; 78 | std::mutex swMapMutex; 79 | 80 | std::string currentServer; 81 | std::string myNick; 82 | 83 | std::regex tablePattern, datePattern, timePattern, dbPattern, modulePattern; 84 | 85 | int state; 86 | int timer; 87 | int infoTimer; 88 | int wdTimer; 89 | time_t maxTime; 90 | 91 | std::string updateChannel; 92 | std::string channelTopic; 93 | std::string bestServer; 94 | std::string wdInfo; 95 | 96 | bool initReady; 97 | bool terminateThread; 98 | bool logIRC; 99 | }; 100 | -------------------------------------------------------------------------------- /ircddb/IRCMessage.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | 4 | #include "IRCMessage.h" 5 | 6 | IRCMessage::IRCMessage() 7 | { 8 | numParams = 0; 9 | prefixParsed = false; 10 | } 11 | 12 | IRCMessage::IRCMessage(const std::string &toNick, const std::string &msg) 13 | { 14 | command = "PRIVMSG"; 15 | numParams = 2; 16 | params.push_back(toNick); 17 | params.push_back(msg); 18 | prefixParsed = false; 19 | } 20 | 21 | IRCMessage::IRCMessage(const std::string &cmd) 22 | { 23 | command = cmd; 24 | numParams = 0; 25 | prefixParsed = false; 26 | } 27 | 28 | IRCMessage::~IRCMessage() 29 | { 30 | } 31 | 32 | 33 | void IRCMessage::addParam(const std::string &p) 34 | { 35 | params.push_back(p); 36 | numParams = params.size(); 37 | } 38 | 39 | int IRCMessage::getParamCount() 40 | { 41 | return params.size(); 42 | } 43 | 44 | std::string IRCMessage::getParam(int pos) 45 | { 46 | return params[pos]; 47 | } 48 | 49 | std::string IRCMessage::getCommand() 50 | { 51 | return command; 52 | } 53 | 54 | 55 | void IRCMessage::parsePrefix() 56 | { 57 | unsigned int i; 58 | 59 | for (i=0; i < 3; i++) 60 | { 61 | prefixComponents.push_back(""); 62 | } 63 | 64 | int state = 0; 65 | 66 | for (i=0; i < prefix.length(); i++) 67 | { 68 | char c = prefix.at(i); 69 | 70 | switch (c) 71 | { 72 | case '!': 73 | state = 1; // next is name 74 | break; 75 | 76 | case '@': 77 | state = 2; // next is host 78 | break; 79 | 80 | default: 81 | prefixComponents[state].append(1, c); 82 | break; 83 | } 84 | } 85 | 86 | prefixParsed = true; 87 | } 88 | 89 | std::string &IRCMessage::getPrefixNick() 90 | { 91 | if (!prefixParsed) 92 | { 93 | parsePrefix(); 94 | } 95 | 96 | return prefixComponents[0]; 97 | } 98 | 99 | std::string &IRCMessage::getPrefixName() 100 | { 101 | if (!prefixParsed) 102 | { 103 | parsePrefix(); 104 | } 105 | 106 | return prefixComponents[1]; 107 | } 108 | 109 | std::string &IRCMessage::getPrefixHost() 110 | { 111 | if (!prefixParsed) 112 | { 113 | parsePrefix(); 114 | } 115 | 116 | return prefixComponents[2]; 117 | } 118 | 119 | void IRCMessage::composeMessage(std::string &output) 120 | { 121 | std::string o; 122 | 123 | if (prefix.length() > 0) 124 | { 125 | o = std::string(":") + prefix + ' '; 126 | } 127 | 128 | o += command; 129 | 130 | for (int i=0; i < numParams; i++) 131 | { 132 | if (i == (numParams - 1)) 133 | { 134 | o += (std::string(" :") + params[i]); 135 | } 136 | else 137 | { 138 | o += (std::string(" ") + params[i]); 139 | } 140 | } 141 | 142 | o += std::string("\r\n"); 143 | 144 | output = o; 145 | } 146 | -------------------------------------------------------------------------------- /ircddb/IRCMessage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class IRCMessage 6 | { 7 | public: 8 | IRCMessage(); 9 | IRCMessage(const std::string& toNick, const std::string& msg); 10 | IRCMessage(const std::string& command); 11 | ~IRCMessage(); 12 | 13 | std::string prefix; 14 | std::string command; 15 | std::vector params; 16 | 17 | int numParams; 18 | 19 | std::string &getPrefixNick(); 20 | std::string &getPrefixName(); 21 | std::string &getPrefixHost(); 22 | 23 | void composeMessage (std::string& output); 24 | 25 | void addParam(const std::string &p); 26 | 27 | std::string getCommand(); 28 | 29 | std::string getParam(int pos); 30 | 31 | int getParamCount(); 32 | 33 | private: 34 | void parsePrefix(); 35 | 36 | std::vector prefixComponents; 37 | bool prefixParsed; 38 | }; 39 | -------------------------------------------------------------------------------- /ircddb/IRCMessageQueue.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CIRCDDB - ircDDB client library in C++ 3 | 4 | Based on code by: 5 | Copyright (C) 2010 Michael Dirska, DL1BFF (dl1bff@mdx.de) 6 | 7 | Completely rewritten by: 8 | Copyright (c) 2017 by Thomas A. Early N7TAE 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 2 of the License, or 13 | (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | */ 23 | 24 | #include "IRCMessageQueue.h" 25 | 26 | IRCMessageQueue::IRCMessageQueue() 27 | { 28 | m_eof = false; 29 | } 30 | 31 | IRCMessageQueue::~IRCMessageQueue() 32 | { 33 | accessMutex.lock(); 34 | while (! m_queue.empty()) 35 | { 36 | delete m_queue.front(); 37 | m_queue.pop(); 38 | } 39 | accessMutex.unlock(); 40 | } 41 | 42 | bool IRCMessageQueue::isEOF() 43 | { 44 | return m_eof; 45 | } 46 | 47 | void IRCMessageQueue::signalEOF() 48 | { 49 | m_eof = true; 50 | } 51 | 52 | bool IRCMessageQueue::messageAvailable() 53 | { 54 | accessMutex.lock(); 55 | bool retv = ! m_queue.empty(); 56 | accessMutex.unlock(); 57 | return retv; 58 | } 59 | 60 | IRCMessage *IRCMessageQueue::peekFirst() 61 | { 62 | accessMutex.lock(); 63 | IRCMessage *msg = m_queue.empty() ? NULL : m_queue.front(); 64 | accessMutex.unlock(); 65 | return msg; 66 | } 67 | 68 | IRCMessage *IRCMessageQueue::getMessage() 69 | { 70 | accessMutex.lock(); 71 | IRCMessage *msg = m_queue.empty() ? NULL : m_queue.front(); 72 | if (msg) 73 | m_queue.pop(); 74 | accessMutex.unlock(); 75 | return msg; 76 | } 77 | 78 | void IRCMessageQueue::putMessage(IRCMessage *m) 79 | { 80 | accessMutex.lock(); 81 | m_queue.push(m); 82 | accessMutex.unlock(); 83 | } 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ircddb/IRCMessageQueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | CIRCDDB - ircDDB client library in C++ 3 | 4 | Based on original code by: 5 | Copyright (C) 2010 Michael Dirska, DL1BFF (dl1bff@mdx.de) 6 | 7 | Completely rewritten by: 8 | Copyright (c) 2017 by Thomas A. Early N7TAE 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 2 of the License, or 13 | (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | */ 23 | 24 | #pragma once 25 | 26 | #include 27 | #include 28 | 29 | #include "IRCMessage.h" 30 | 31 | class IRCMessageQueue 32 | { 33 | public: 34 | IRCMessageQueue(); 35 | ~IRCMessageQueue(); 36 | 37 | bool isEOF(); 38 | void signalEOF(); 39 | bool messageAvailable(); 40 | IRCMessage *getMessage(); 41 | IRCMessage *peekFirst(); 42 | void putMessage(IRCMessage *m); 43 | 44 | private: 45 | bool m_eof; 46 | std::mutex accessMutex; 47 | std::queue m_queue; 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /ircddb/IRCProtocol.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IRCMessageQueue.h" 4 | class IRCDDBApp; 5 | 6 | class IRCProtocol 7 | { 8 | public: 9 | IRCProtocol() {} 10 | 11 | void Init(IRCDDBApp *app, const std::string &callsign, const std::string &password, const std::string &channel, const std::string &versionInfo); 12 | 13 | ~IRCProtocol(); 14 | 15 | void setNetworkReady(bool state); 16 | 17 | bool processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ); 18 | 19 | private: 20 | void chooseNewNick(); 21 | 22 | std::vector nicks; 23 | std::string password; 24 | std::string channel; 25 | std::string name; 26 | std::string currentNick; 27 | std::string versionInfo; 28 | 29 | int state; 30 | int timer; 31 | int pingTimer; 32 | 33 | std::string debugChannel; 34 | 35 | IRCDDBApp *app; 36 | }; 37 | -------------------------------------------------------------------------------- /ircddb/IRCReceiver.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "IRCutils.h" 6 | #include "IRCMessage.h" 7 | #include "IRCReceiver.h" 8 | 9 | void IRCReceiver::Init(CTCPReaderWriterClient *sock, IRCMessageQueue *q) 10 | { 11 | ircSock = sock; 12 | recvQ = q; 13 | } 14 | 15 | IRCReceiver::~IRCReceiver() 16 | { 17 | } 18 | 19 | bool IRCReceiver::startWork() 20 | { 21 | terminateThread = false; 22 | rec_thread = std::async(std::launch::async, &IRCReceiver::Entry, this); 23 | return true; 24 | } 25 | 26 | void IRCReceiver::stopWork() 27 | { 28 | terminateThread = true; 29 | rec_thread.get(); 30 | } 31 | 32 | int IRCReceiver::doRead(CTCPReaderWriterClient *ircSock, char *buf, int buf_size) 33 | { 34 | struct timeval tv; 35 | tv.tv_sec = 1; 36 | tv.tv_usec = 0; 37 | fd_set rdset; 38 | fd_set errset; 39 | 40 | int fd = ircSock->GetFD(); 41 | FD_ZERO(&rdset); 42 | FD_ZERO(&errset); 43 | FD_SET(fd, &rdset); 44 | FD_SET(fd, &errset); 45 | 46 | int res; 47 | 48 | res = select(fd+1, &rdset, NULL, &errset, &tv); 49 | 50 | if ( res < 0 ) 51 | { 52 | printf("IRCReceiver::doread: select() error.\n"); 53 | return -1; 54 | } 55 | else if ( res > 0 ) 56 | { 57 | if (FD_ISSET(fd, &errset)) 58 | { 59 | printf("IRCReceiver::doRead: FD_ISSET error\n"); 60 | return -1; 61 | } 62 | 63 | if (FD_ISSET(fd, &rdset)) 64 | { 65 | res = ircSock->Read((unsigned char *)buf, buf_size); 66 | 67 | if (res < 0) 68 | { 69 | printf("IRCReceiver::doRead: recv error\n"); 70 | return -1; 71 | } 72 | else if (res == 0) 73 | { 74 | printf("IRCReceiver::doRead: EOF read==0\n"); 75 | return -1; 76 | } 77 | else 78 | return res; 79 | } 80 | 81 | } 82 | 83 | return 0; 84 | } 85 | 86 | void IRCReceiver::Entry() 87 | { 88 | IRCMessage *m = new IRCMessage(); 89 | 90 | int i; 91 | int state = 0; 92 | 93 | while (!terminateThread) 94 | { 95 | char buf[200]; 96 | int r = doRead(ircSock, buf, sizeof buf); 97 | 98 | if (r < 0) 99 | { 100 | recvQ->signalEOF(); 101 | delete m; // delete unfinished IRCMessage 102 | break; 103 | } 104 | 105 | for (i=0; i 0) 110 | { 111 | if (b == '\n') 112 | { 113 | recvQ->putMessage(m); 114 | m = new IRCMessage(); 115 | state = 0; 116 | } 117 | else if (b == '\r') 118 | { 119 | // do nothing 120 | } 121 | else switch (state) 122 | { 123 | case 0: 124 | if (b == ':') 125 | { 126 | state = 1; // prefix 127 | } 128 | else if (b == ' ') 129 | { 130 | // do nothing 131 | } 132 | else 133 | { 134 | m->command.push_back(b); 135 | state = 2; // command 136 | } 137 | break; 138 | 139 | case 1: 140 | if (b == ' ') 141 | { 142 | state = 2; // command is next 143 | } 144 | else 145 | { 146 | m->prefix.push_back(b); 147 | } 148 | break; 149 | 150 | case 2: 151 | if (b == ' ') 152 | { 153 | state = 3; // params 154 | m->numParams = 1; 155 | m->params.push_back(""); 156 | } 157 | else 158 | { 159 | m->command.push_back(b); 160 | } 161 | break; 162 | 163 | case 3: 164 | if (b == ' ') 165 | { 166 | m->numParams++; 167 | if (m->numParams >= 15) 168 | { 169 | state = 5; // ignore the rest 170 | } 171 | 172 | m->params.push_back(""); 173 | } 174 | else if ((b == ':') && (m->params[m->numParams - 1].length() == 0)) 175 | { 176 | state = 4; // rest of line is this param 177 | } 178 | else 179 | { 180 | m->params[m->numParams - 1].push_back(b); 181 | } 182 | break; 183 | 184 | case 4: 185 | m->params[m->numParams - 1].push_back(b); 186 | break; 187 | } // switch 188 | } // if 189 | } // for 190 | } // while 191 | 192 | return; 193 | } 194 | -------------------------------------------------------------------------------- /ircddb/IRCReceiver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "IRCMessageQueue.h" 4 | #include "../TCPReaderWriterClient.h" 5 | 6 | class IRCReceiver 7 | { 8 | public: 9 | IRCReceiver() {} 10 | void Init(CTCPReaderWriterClient *ircSock, IRCMessageQueue *q); 11 | virtual ~IRCReceiver(); 12 | bool startWork(); 13 | void stopWork(); 14 | 15 | protected: 16 | virtual void Entry(); 17 | 18 | private: 19 | static int doRead(CTCPReaderWriterClient *ircSock, char *buf, int buf_size); 20 | 21 | CTCPReaderWriterClient *ircSock; 22 | bool terminateThread; 23 | int sock; 24 | IRCMessageQueue *recvQ; 25 | std::future rec_thread; 26 | }; 27 | -------------------------------------------------------------------------------- /ircddb/IRCutils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "IRCutils.h" 19 | // not needed, defined in /usr/include/features.h 20 | //#define _XOPEN_SOURCE 21 | 22 | time_t parseTime(const std::string str) 23 | { 24 | struct tm stm; 25 | strptime(str.c_str(), "%Y-%m-%d %H:%M:%S", &stm); 26 | return mktime(&stm); 27 | } 28 | 29 | std::vector stringTokenizer(const std::string &s) 30 | { 31 | std::stringstream ss(s); 32 | std::istream_iterator it(ss); 33 | std::istream_iterator end; 34 | std::vector result(it, end); 35 | return result; 36 | } 37 | 38 | void safeStringCopy (char *dest, const char *src, unsigned int buf_size) 39 | { 40 | unsigned int i = 0; 41 | 42 | while (i<(buf_size - 1) && src[i] != 0) 43 | { 44 | dest[i] = src[i]; 45 | i++; 46 | } 47 | 48 | dest[i] = 0; 49 | } 50 | 51 | char *getCurrentTime(void) 52 | { 53 | time_t now = time(NULL); 54 | struct tm* tm; 55 | struct tm tm_buf; 56 | static char buffer[25]; 57 | 58 | gmtime_r(&now, &tm_buf); 59 | tm = &tm_buf; 60 | 61 | strftime(buffer, sizeof buffer, "%Y-%m-%d %H:%M:%S", tm); 62 | 63 | return buffer; 64 | } 65 | 66 | void ToUpper(std::string &str) 67 | { 68 | for (auto it=str.begin(); it!=str.end(); it++) 69 | { 70 | if (islower(*it)) 71 | *it = toupper(*it); 72 | } 73 | } 74 | 75 | void ToLower(std::string &str) 76 | { 77 | for (auto it=str.begin(); it!=str.end(); it++) 78 | { 79 | if (isupper(*it)) 80 | *it = tolower(*it); 81 | } 82 | } 83 | 84 | 85 | void ReplaceChar(std::string &str, char from, char to) 86 | { 87 | for (auto it=str.begin(); it!=str.end(); it++) 88 | { 89 | if (from == *it) 90 | *it = to; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ircddb/IRCutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 by Scott Lawson KI4LKF 3 | * Copyright (C) 2017-2018 by Thomas Early N7TAE 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | time_t parseTime(const std::string str); 27 | 28 | std::vector stringTokenizer(const std::string &str); 29 | 30 | void safeStringCopy(char * dest, const char * src, unsigned int buf_size); 31 | 32 | char *getCurrentTime(void); 33 | 34 | void ToUpper(std::string &str); 35 | 36 | void ToLower(std::string &str); 37 | 38 | void ReplaceChar(std::string &str, char from, char to); 39 | -------------------------------------------------------------------------------- /qn.dvap.cfg: -------------------------------------------------------------------------------- 1 | # A Simple Configuration for a 2M DVAP 2 | 3 | ircddb_login='Q1ABC' 4 | 5 | module_c='dvap' # change to 'module_b' if you have a 70cm dvap 6 | module_c_serial_number='AP123456' # your serial number is visible through the case 7 | module_c_frequency=146.5 # in MHz, chose a quiet frequency 8 | 9 | #dplus_authorize=true # uncomment if you want to link to the legacy D-Plus system 10 | -------------------------------------------------------------------------------- /qn.itap.cfg: -------------------------------------------------------------------------------- 1 | # A Simple Configuration File for an ICOM using Terminal and Access Point Mode 2 | 3 | ircddb_login='Q1ABC' 4 | 5 | module_c='itap' 6 | #module_c_ap_mode=true # uncomment if you are operating your ICOM radio in Access Point Mode 7 | 8 | #dplus_authorize=true # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters 9 | -------------------------------------------------------------------------------- /qn.mmdvm.cfg: -------------------------------------------------------------------------------- 1 | # Example for an MMDVMHost-Based Hot-Spot 2 | # 3 | # Please see qn.everything.cfg for many configurable items. 4 | # New-bees beware, it is possible to configure your system 5 | # to a non-functional state. Nearly all configure items 6 | # already have good default values, but the two below 7 | # HAVE TO BE SET BY YOU!!! 8 | 9 | ircddb_login='Q1ABC' 10 | 11 | module_x='mmdvm' 12 | 13 | # Change the "x" to the lowercase equivilent of the module assignment in you MMDVM.qn initialization file. 14 | # Use B in the .ini file and b in this file for 70cm and C and c for 2M. See the qn.everything.cfg file if you want to include 15 | # location data for your repeater/hot-spot. (Location data in your MMDVM.qn ini file will 16 | # not make it to the D-Star network.) 17 | 18 | #dplus_authorize='true' # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters 19 | -------------------------------------------------------------------------------- /qndtmf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2011 by Scott Lawson KI4LKF 4 | # Copyright (C) 2018 by Thomas A. Early N7TAE 5 | # 6 | # This script finds files in the /tmp directory 7 | # The files have a name like x_mod_DTMF_NOTIFY, where x is one of A B or C, the local module 8 | # The contents of these files can be as follows: 9 | 10 | # Example: # will unlink local module, " U" 11 | # Example: B75703 will link local module to XRF757 C 12 | # Example: D00617 will link local module to DCS006 Q 13 | # Example: *00103 will link local module to REF001 C 14 | # Example: 0 or 00 will report status of the link, " I" 15 | # Example: ##08 will execute the exec_H.sh script (shutdown the system) 16 | # Please note that scripts exec_[0-9].sh are not accessible from DTMF. 17 | 18 | # We set this to spaces, it will be set later 19 | 20 | GetLetter () { 21 | local i 22 | if [[ $1 == +([0-9]) ]]; then 23 | i=`expr $1 - 1` 24 | if [ $i -ge 0 ] && [ $i -lt 26 ]; then 25 | LETTER=${LETTERS[$i]} 26 | return 27 | fi 28 | fi 29 | LETTER=$BAD 30 | } 31 | 32 | LUSER=" " 33 | LETTERS=( {A..Z} ) 34 | BAD='bad' 35 | 36 | cd /tmp 37 | echo started at `date` 38 | 39 | while [[ 1 ]] 40 | do 41 | for i in `ls ?_mod_DTMF_NOTIFY 2>/dev/null` 42 | do 43 | echo found file $i at `date` 44 | LOCAL_BAND=${i:0:1} 45 | if [[ "$LOCAL_BAND" == 'A' ]] || [[ "$LOCAL_BAND" == 'B' ]] || [[ "$LOCAL_BAND" == 'C' ]]; then 46 | CMD=`head -n 1 $i 2>/dev/null` 47 | LUSER=`tail -n 1 $i 2>/dev/null` 48 | echo "... with these contents: " $CMD " " $LUSER 49 | if [[ "$CMD" == '#' ]]; then 50 | echo Unlinking local band $LOCAL_BAND requested by $LUSER 51 | qnremote ${LOCAL_BAND} "$LUSER" U >/dev/null 2>&1 52 | echo 53 | elif [[ "$CMD" == '0' ]] || [[ "$CMD" == '00' ]]; then 54 | echo Link Status on local band $LOCAL_BAND requested by $LUSER 55 | qnremote ${LOCAL_BAND} "$LUSER" I >/dev/null 2>&1 56 | echo 57 | elif [[ "$CMD" == '**' ]]; then 58 | echo Load Hosts on local band $LOCAL_BAND requested by $LUSER 59 | qnremote ${LOCAL_BAND} "$LUSER" F >/dev/null 2>&1 60 | else 61 | if [ ${#CMD} -eq 4 ] && [[ ${CMD:0:2} == '##' ]]; then 62 | GetLetter ${CMD:2:2} 63 | if [[ "$LETTER" == "$BAD" ]]; then 64 | echo "bad script letter index: '${CMD:2:2}'" 65 | qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad DTMF CMD" 66 | else 67 | qnremote $LOCAL_BAND $LUSER ${LETTER}X >/dev/null 2>&1 68 | fi 69 | elif [ ${#CMD} -eq 6 ]; then 70 | 71 | PFX=${CMD:0:1} 72 | if [[ "$PFX" = 'B' ]]; then 73 | RMT=XRF 74 | elif [[ "$PFX" = 'D' ]]; then 75 | RMT=DCS 76 | elif [[ "$PFX" = '*' ]]; then 77 | RMT=REF 78 | else 79 | RMT=$BAD 80 | fi 81 | 82 | REMOTE_NODE=${CMD:1:3} 83 | if [[ $REMOTE_NODE != +([0-9]) ]]; then 84 | REMOTE_NODE=$BAD 85 | fi 86 | 87 | GetLetter ${CMD:4:2} 88 | REMOTE_BAND=$LETTER 89 | 90 | if [[ "$RMT" == "$BAD" ]] || [[ "$REMOTE_NODE" == "$BAD" ]] || [[ "$REMOTE_BAND" == "$BAD" ]]; then 91 | echo "Bad link command: '$CMD'" 92 | qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad Link CMD" 93 | else 94 | echo linking local band $LOCAL_BAND to remote node ${RMT}${REMOTE_NODE} $REMOTE_BAND requested by $LUSER 95 | qnremote ${LOCAL_BAND} "$LUSER" ${RMT}${REMOTE_NODE}${REMOTE_BAND}L >/dev/null 2>&1 96 | echo 97 | fi 98 | else 99 | echo "Bad command: '$CMD'" 100 | qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad CMD" 101 | fi 102 | fi 103 | else 104 | echo "Local band '${LOCAL_BAND}' is bad" 105 | fi 106 | rm -f $i 107 | done 108 | sleep 2 109 | done 110 | 111 | exit 0 112 | -------------------------------------------------------------------------------- /system/gateway.timer: -------------------------------------------------------------------------------- 1 | [Timer] 2 | OnStartupSec=45 3 | 4 | [Install] 5 | WantedBy=multi-user.target 6 | -------------------------------------------------------------------------------- /system/mmdvm.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=XXX 3 | After=systemd-user-session.service qnrelay.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/YYY.qn 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/mmdvm.timer: -------------------------------------------------------------------------------- 1 | [Timer] 2 | OnStartupSec=30 3 | 4 | [Install] 5 | WantedBy=multi-user.target 6 | 7 | -------------------------------------------------------------------------------- /system/qndash.service.80: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetGateway Dashboard 3 | Requires=network.target 4 | After=systemd-user-session.service network.target 5 | 6 | [Service] 7 | Type=simple 8 | WorkingDirectory=/usr/local/www 9 | ExecStart=/usr/bin/php -S 0.0.0.0:80 10 | Restart=always 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /system/qndtmf.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetDTMF 3 | After=systemd-user-session.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/qndtmf 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/qndvap.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetDVAP 3 | Before=systemd-user-session.service qngateway.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/qndvrptr.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetDVRPTR 3 | Before=systemd-user-session.service qngateway.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/qngateway.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetGateway 3 | Requires=network.target 4 | After=systemd-user-session.service network.target 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/usr/local/bin/qngateway /usr/local/etc/qn.cfg 9 | Restart=always 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /system/qnitap.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetITAP 3 | Before=systemd-user-session.service qngateway.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/qnlink.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetLink 3 | Requires=network.target 4 | After=systemd-user-session.service network.target 5 | Before=systemd-user-session.service qngateway.service 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/usr/local/bin/qnlink /usr/local/etc/qn.cfg 10 | Restart=always 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /system/qnmodem.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetModem 3 | Before=systemd-user-session.service qngateway.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /system/qnrelay.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QnetRelay 3 | Before=systemd-user-session.service qngateway.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | --------------------------------------------------------------------------------