diff --git a/BaseCommon/TCPReaderWriterServer.cpp b/BaseCommon/TCPReaderWriterServer.cpp new file mode 100644 index 0000000..181a3bb --- /dev/null +++ b/BaseCommon/TCPReaderWriterServer.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2011 by Jonathan Naylor G4KLX + * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "TCPReaderWriterServer.h" +#include "Log.h" +#include "NetUtils.h" + +#include +#include + + +CTCPReaderWriterServer::CTCPReaderWriterServer(const std::string& address, unsigned int port) : +CThread("TCP server"), +m_address(address), +m_port(port), +m_fd(-1), +m_client(NULL), +m_stopped(false) +{ + assert(port > 0U); +} + +CTCPReaderWriterServer::~CTCPReaderWriterServer() +{ +} + +bool CTCPReaderWriterServer::start() +{ + bool ret = open(); + if (!ret) { + close(); + return false; + } + + Create(); + Run(); + + return true; +} + +int CTCPReaderWriterServer::read(unsigned char* buffer, unsigned int length, unsigned int secs) +{ + assert(buffer != NULL); + assert(length > 0U); + + if (m_client != NULL) { + int ret = m_client->read(buffer, length, secs); + if (ret < 0) { + CLog::logInfo("Lost TCP connection to port %u", m_port); + + m_client->close(); + delete m_client; + m_client = NULL; + + open(); + + return 0; + } + + return ret; + } + + return 0; +} + +bool CTCPReaderWriterServer::write(const unsigned char* buffer, unsigned int length) +{ + assert(buffer != NULL); + assert(length > 0U); + + if (m_client != NULL) { + bool ret = m_client->write(buffer, length); + if (!ret) { + CLog::logInfo("Lost TCP connection to port %u", m_port); + + m_client->close(); + delete m_client; + m_client = NULL; + + open(); + + return false; + } + + return true; + } + + return true; +} + +void* CTCPReaderWriterServer::Entry() +{ +#ifndef DEBUG_DSTARGW + try { +#endif + while (!m_stopped) { + int ret = accept(); + switch (ret) { + case -2: + break; + case -1: + break; + default: + CLog::logInfo("Incoming TCP connection to port %u", m_port); + m_client = new CTCPReaderWriterClient(ret); + close(); + break; + } + + Sleep(1000UL); + } + + if (m_client != NULL) { + m_client->close(); + delete m_client; + } + + close(); + +#ifndef DEBUG_DSTARGW + } + catch (std::exception& e) { + std::string message(e.what()); + CLog::logError("Exception raised in the TCP Reader-Writer Server thread - \"%s\"", message.c_str()); + } + catch (...) { + CLog::logError("Unknown exception raised in the TCP Reader-Writer Server thread"); + } +#endif + + return NULL; +} + +void CTCPReaderWriterServer::stop() +{ + m_stopped = true; + + Wait(); +} + +bool CTCPReaderWriterServer::open() +{ + m_fd = ::socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + CLog::logError("Cannot create the TCP server socket, err=%d", errno); + return false; + } + + struct sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + if (m_address.empty()) + addr.sin_addr.s_addr = htonl(INADDR_ANY); + else + addr.sin_addr = lookup(m_address); + + if (addr.sin_addr.s_addr == INADDR_NONE) { + CLog::logError("The address is invalid - %s", m_address.c_str()); + close(); + return false; + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + CLog::logError("Cannot set the TCP server socket option, err=%d", errno); + close(); + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { + CLog::logError("Cannot bind the TCP server address, err=%d", errno); + close(); + return false; + } + + ::listen(m_fd, 5); + + return true; +} + +int CTCPReaderWriterServer::accept() +{ + if (m_fd == -1) + return -1; + + // Check that the accept() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(__WINDOWS__) + FD_SET((unsigned int)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return after timeout + timeval tv; + tv.tv_sec = 0L; + tv.tv_usec = 0L; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { + CLog::logError("Error returned from TCP server select, err=%d", errno); + return -2; + } + +#if defined(__WINDOWS__) + if (!FD_ISSET((unsigned int)m_fd, &readFds)) + return -1; +#else + if (!FD_ISSET(m_fd, &readFds)) + return -1; +#endif + + struct sockaddr_in addr; +#if defined(__WINDOWS__) + int len = sizeof(struct sockaddr_in); +#else + socklen_t len = sizeof(struct sockaddr_in); +#endif + + ret = ::accept(m_fd, (sockaddr*)&addr, &len); + if (ret < 0) { + CLog::logError("Error returned from TCP server accept, err=%d", errno); + } + + return ret; +} + +void CTCPReaderWriterServer::close() +{ + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +} + +in_addr CTCPReaderWriterServer::lookup(const std::string& hostname) const +{ + in_addr addrv4; + addrv4.s_addr = INADDR_NONE; + + sockaddr_storage addr; + auto res = CNetUtils::lookupV4(hostname, addr); + + if(res) { + addrv4 = TOIPV4(addr)->sin_addr; + } + + return addrv4; +} diff --git a/BaseCommon/TCPReaderWriterServer.h b/BaseCommon/TCPReaderWriterServer.h new file mode 100644 index 0000000..25a6deb --- /dev/null +++ b/BaseCommon/TCPReaderWriterServer.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 by Jonathan Naylor G4KLX + * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include "TCPReaderWriterClient.h" +#include "Thread.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CTCPReaderWriterServer : public CThread { +public: + CTCPReaderWriterServer(const std::string& address, unsigned int port); + virtual ~CTCPReaderWriterServer(); + + virtual bool start(); + + virtual bool write(const unsigned char* buffer, unsigned int length); + virtual int read(unsigned char* buffer, unsigned int length, unsigned int secs); + + virtual void stop(); + + virtual void* Entry(); + +private: + std::string m_address; + unsigned short m_port; + int m_fd; + CTCPReaderWriterClient* m_client; + bool m_stopped; + + bool open(); + int accept(); + void close(); + in_addr lookup(const std::string& hostname) const; +}; diff --git a/Common/DRATSServer.cpp b/Common/DRATSServer.cpp new file mode 100644 index 0000000..d83bbc4 --- /dev/null +++ b/Common/DRATSServer.cpp @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarDefines.h" +#include "DRATSServer.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include + +// #define LOOPBACK + +const unsigned int BUFFER_LENGTH = 30000U; + +CDRATSServer::CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler) : +CThread("DRats"), +m_address(address), +m_port(port), +m_callsign(callsign), +m_handler(handler), +m_socket(NULL), +m_stopped(false), +m_readState(SS_FIRST), +m_readBuffer(NULL), +m_readLength(0U), +m_readPos(0U), +m_readEnd(false), +m_writeText(NULL), +m_writeState(SS_FIRST), +m_writeBuffer(NULL), +m_writeLength(0U) +{ + assert(handler != NULL); + assert(port > 0U); + + m_readBuffer = new unsigned char[BUFFER_LENGTH]; + m_writeBuffer = new unsigned char[BUFFER_LENGTH]; + m_writeText = new unsigned char[6U]; +} + +CDRATSServer::~CDRATSServer() +{ + delete[] m_readBuffer; + delete[] m_writeBuffer; + delete[] m_writeText; +} + +bool CDRATSServer::open() +{ + m_socket = new CTCPReaderWriterServer(m_address, m_port); + bool ret = m_socket->start(); + if (!ret) { + delete m_socket; + m_socket = NULL; + return false; + } + + Create(); + Run(); + + return true; +} + +void CDRATSServer::writeHeader(const CHeaderData&) +{ + m_writeState = SS_FIRST; + + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; +} + +void CDRATSServer::writeData(const CAMBEData& data) +{ + // Sync data isn't sent on + if (data.isSync()) { + m_writeState = SS_FIRST; + return; + } + + if (data.isEnd()) { + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; + return; + } + + unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES]; + unsigned int length = data.getData(buffer, DV_FRAME_MAX_LENGTH_BYTES); + + if (length != DV_FRAME_LENGTH_BYTES) + return; + + unsigned char byte1 = buffer[VOICE_FRAME_LENGTH_BYTES + 0U] ^ SCRAMBLER_BYTE1; + unsigned char byte2 = buffer[VOICE_FRAME_LENGTH_BYTES + 1U] ^ SCRAMBLER_BYTE2; + unsigned char byte3 = buffer[VOICE_FRAME_LENGTH_BYTES + 2U] ^ SCRAMBLER_BYTE3; + + switch (m_writeState) { + case SS_FIRST: + m_writeText[0U] = byte1; + m_writeText[1U] = byte2; + m_writeText[2U] = byte3; + m_writeState = SS_SECOND; + return; + + case SS_SECOND: + m_writeText[3U] = byte1; + m_writeText[4U] = byte2; + m_writeText[5U] = byte3; + m_writeState = SS_FIRST; + break; + } + + if ((m_writeText[0U] & SLOW_DATA_TYPE_MASK) != SLOW_DATA_TYPE_GPS) + return; + + length = m_writeText[0U] & 0x07; // Maximum value of 5 + if (length > 5U) + length = 5U; + + for (unsigned int i = 0U; i < length; i++) { + m_writeBuffer[m_writeLength++] = m_writeText[i + 1U]; + + // Check for [EOB] in the buffer to signal the end of the D-RATS data. + // To allow strstr() to run correctly + m_writeBuffer[m_writeLength] = 0x00U; + + if (::strstr((char*)m_writeBuffer, "[EOB]") != NULL) { + if (m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; + } + } +} + +void CDRATSServer::writeEnd() +{ + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; +} + +void* CDRATSServer::Entry() +{ + CLog::logInfo("Starting the D-RATS Server thread for %s", m_callsign.c_str()); + + bool sending = false; + unsigned int id = 0U; + + unsigned char seqNo = 0U; + unsigned int sent = 0U; + + std::chrono::high_resolution_clock::time_point time; + +#ifndef DEBUG_DSTARGW + try { +#endif + while (!m_stopped) { + serviceSocket(); + + if (m_readEnd && !sending) { + id = CHeaderData::createId(); + + // Write header + CHeaderData header; + header.setMyCall1(m_callsign); + header.setMyCall2("DATA"); + header.setYourCall("CQCQCQ "); + header.setId(id); + +#if defined(LOOPBACK) + writeHeader(header); +#else + m_handler->process(header, DIR_INCOMING, AS_DRATS); +#endif + + m_readState = SS_FIRST; + m_readPos = 0U; + sending = true; + seqNo = 0U; + sent = 0U; + + time = std::chrono::high_resolution_clock::now(); + } + + if (m_readEnd && sending) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - time).count() / DSTAR_FRAME_TIME_MS; + + while (sent < needed && sending) { + // Write AMBE data + CAMBEData data; + data.setId(id); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_readState = SS_FIRST; + } else { + if (m_readState == SS_FIRST) { + unsigned char readText[3U]; + ::memset(readText, 'f', 3U); + + unsigned int length = m_readLength - m_readPos; + unsigned char bytes = 5U; + if (length < 5U) + bytes = length; + + readText[0U] = SLOW_DATA_TYPE_GPS | bytes; + + for (unsigned int i = 0U; i < 2U && m_readPos < m_readLength; i++) + readText[i + 1U] = m_readBuffer[m_readPos++]; + + readText[0U] ^= SCRAMBLER_BYTE1; + readText[1U] ^= SCRAMBLER_BYTE2; + readText[2U] ^= SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + m_readState = SS_SECOND; + } else { + unsigned char readText[3U]; + ::memset(readText, 'f', 3U); + + for (unsigned int i = 0U; i < 3U && m_readPos < m_readLength; i++) + readText[i] = m_readBuffer[m_readPos++]; + + readText[0U] ^= SCRAMBLER_BYTE1; + readText[1U] ^= SCRAMBLER_BYTE2; + readText[2U] ^= SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + m_readState = SS_FIRST; + } + } + + data.setSeq(seqNo); + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + sent++; + +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + if (m_readPos == m_readLength) { + if (m_readState == SS_SECOND) { + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + + unsigned char readText[3U]; + readText[0U] = 'f' ^ SCRAMBLER_BYTE1; + readText[1U] = 'f' ^ SCRAMBLER_BYTE2; + readText[2U] = 'f' ^ SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + data.setSeq(seqNo); + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + sent++; +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + } + + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + + if (seqNo == 0U) + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + else + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, NULL_SLOW_DATA_BYTES, DATA_FRAME_LENGTH_BYTES); + + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + data.setSeq(seqNo); + data.setEnd(true); + sent++; +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + m_readLength = 0U; + m_readPos = 0U; + m_readEnd = false; + sending = false; + sent = 0U; + } + + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + } + } + + // 50ms + Sleep(50UL); + } + + if (m_socket != NULL) + m_socket->stop(); +#ifndef DEBUG_DSTARGW + } + catch (std::exception& e) { + std::string message(e.what()); + CLog::logError("Exception raised in the D-RATS Server thread - \"%s\""), message.c_str(); + } + catch (...) { + CLog::logError("Unknown exception raised in the D-RATS Server thread"); + } +#endif + + CLog::logInfo("Stopping the D-RATS Server thread for %s", m_callsign.c_str()); + + return NULL; +} + +void CDRATSServer::close() +{ + m_stopped = true; + + Wait(); +} + +void CDRATSServer::serviceSocket() +{ + if (m_socket == NULL) { + m_readLength = 0U; + m_readPos = 0U; + m_readEnd = false; + return; + } + + int len = m_socket->read(m_readBuffer + m_readLength, BUFFER_LENGTH - m_readLength, 0U); + if (len > 0) { + m_readLength += len; + + if (!m_readEnd) { + // To allow strstr() to run correctly + m_readBuffer[m_readLength] = 0x00U; + + if (::strstr((char*)m_readBuffer, "[EOB]") != NULL) { + CUtils::dump("To RF", m_readBuffer, m_readLength); + m_readEnd = true; + } + } + } +} diff --git a/Common/DRATSServer.h b/Common/DRATSServer.h new file mode 100644 index 0000000..0436bcf --- /dev/null +++ b/Common/DRATSServer.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DRATSServer_H +#define DRATSServer_H + +#include "TCPReaderWriterServer.h" +#include "RepeaterCallback.h" +#include "HeaderData.h" +#include "AMBEData.h" +#include "Defs.h" +#include "Thread.h" + +#include + +class CDRATSServer : public CThread { +public: + CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler); + virtual ~CDRATSServer(); + + virtual bool open(); + + virtual void writeHeader(const CHeaderData& header); + virtual void writeData(const CAMBEData& data); + virtual void writeEnd(); + + virtual void close(); + + virtual void* Entry(); + +private: + std::string m_address; + unsigned int m_port; + std::string m_callsign; + IRepeaterCallback* m_handler; + CTCPReaderWriterServer* m_socket; + bool m_stopped; + SLOWDATA_STATE m_readState; + unsigned char* m_readBuffer; + unsigned int m_readLength; + unsigned int m_readPos; + bool m_readEnd; + unsigned char* m_writeText; + SLOWDATA_STATE m_writeState; + unsigned char* m_writeBuffer; + unsigned int m_writeLength; + + void serviceSocket(); +}; + +#endif diff --git a/Common/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp index 35685a6..9f817ca 100644 --- a/Common/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -64,7 +64,7 @@ CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL; CCallsignList* CRepeaterHandler::m_restrictList = NULL; #ifdef USE_DRATS - CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unigned char band3) : + CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : #else CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : #endif diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 328e8fc..b29c2c0 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -260,6 +260,9 @@ bool CDStarGatewayApp::createThread() rptrConfig.reflectorAtStartup, rptrConfig.reflectorReconnect, rptrConfig.frequency, + #ifdef USE_DRATS + true, + #endif rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, diff --git a/Makefile b/Makefile index ce115f6..d9f7720 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ export CPPFLAGS+= -DUSE_GPSD export LDFLAGS+= -lgps endif +export CPPFLAGS+= -DUSE_DRATS .PHONY: all all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests