commit
4add55a64e
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Losely based on https://github.com/jirihnidek/daemon/blob/master/src/daemon.c
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <sys/stat.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "Daemon.h"
|
||||
#include "Log.h"
|
||||
|
||||
int CDaemon::m_pid_fd = -1;
|
||||
std::string CDaemon::m_pidFileName("");
|
||||
|
||||
DAEMONIZE_RESULT CDaemon::daemonise(const std::string& pidFile, const std::string& userName)
|
||||
{
|
||||
// get user
|
||||
struct passwd* user = nullptr;
|
||||
if(!userName.empty() && getuid() == 0) {
|
||||
user = getpwnam(userName.c_str());
|
||||
if(user == nullptr) {
|
||||
CLog::logFatal("Failed to get %s user", userName.c_str());
|
||||
return DR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Create PID file if needed
|
||||
if (!pidFile.empty()) {
|
||||
auto tempFd = tryGetLock(pidFile);
|
||||
if (tempFd < 0) {
|
||||
CLog::logFatal("Failed to acquire lock on pidfile %s : %s", pidFile.c_str(), strerror(errno));
|
||||
return DR_PIDFILE_FAILED;
|
||||
}
|
||||
releaseLock(tempFd, "");
|
||||
|
||||
if(user != nullptr) {
|
||||
int res = chown(pidFile.c_str(), user->pw_uid, user->pw_gid);
|
||||
if(res != 0) {
|
||||
CLog::logFatal("Failed to set ownership of pidfile to user %s : %s", userName.c_str(), strerror(errno));
|
||||
return DR_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// change process ownership
|
||||
if(user != nullptr) {
|
||||
if(setgid(user->pw_gid) != 0) {
|
||||
CLog::logFatal("Failed to set %s GID : %s", userName.c_str(), strerror(errno));
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
if(setuid(user->pw_uid) != 0) {
|
||||
CLog::logFatal("Failed to set %s UID : %s", userName.c_str(), strerror(errno));
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
// Double check it worked (AKA Paranoia)
|
||||
if (setuid(0) != -1){
|
||||
CLog::logFatal("It's possible to regain root - something is wrong!, exiting");
|
||||
return DR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t pid = 0;
|
||||
|
||||
/* Fork off the parent process */
|
||||
pid = fork();
|
||||
|
||||
/* An error occurred */
|
||||
if (pid < 0) {
|
||||
CLog::logFatal("Forking failed, exiting");
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
/* Success: Let the parent terminate */
|
||||
if (pid > 0) {
|
||||
return DR_PARENT;
|
||||
}
|
||||
|
||||
// On success: The child process becomes session leader
|
||||
if (setsid() < 0) {
|
||||
CLog::logFatal("Failed to set session id, exiting");
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
/* Ignore signal sent from child to parent process */
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
#ifdef DOUBLE_FORK
|
||||
// Fork off for the second time. Some litterature says it is best to fork 2 times so that the process never can open a terminal.
|
||||
// However it messes up systemd, event when unit is set as forking
|
||||
pid = fork();
|
||||
|
||||
// An error occurred
|
||||
if (pid < 0) {
|
||||
CLog::logFatal("Second forking failed, exiting");
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
// Success: Let the parent terminate
|
||||
if (pid > 0) {
|
||||
return DR_PARENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set new file permissions
|
||||
umask(0);
|
||||
|
||||
/* Change the working directory to the root directory */
|
||||
/* or another appropriated directory */
|
||||
if(chdir("/") != 0) {
|
||||
CLog::logFatal("Faild to cd, exiting");
|
||||
return DR_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef CLOSE_FILE_DESC
|
||||
// Close all open file descriptors
|
||||
for (int fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2)
|
||||
stdin = fopen("/dev/null", "r");
|
||||
stdout = fopen("/dev/null", "w+");
|
||||
stderr = fopen("/dev/null", "w+");
|
||||
#endif
|
||||
|
||||
// Try to write PID of daemon to lockfile
|
||||
if (!pidFile.empty())
|
||||
{
|
||||
m_pid_fd = tryGetLock(pidFile);
|
||||
if (m_pid_fd < 0) {
|
||||
/* Can't open lockfile */
|
||||
return DR_PIDFILE_FAILED;
|
||||
}
|
||||
if (lockf(m_pid_fd, F_TLOCK, 0) < 0) {
|
||||
/* Can't lock file */
|
||||
return DR_PIDFILE_FAILED;
|
||||
}
|
||||
|
||||
m_pidFileName.assign(pidFile);
|
||||
|
||||
/* Get current PID */
|
||||
char str[256];
|
||||
sprintf(str, "%d\n", getpid());
|
||||
/* Write PID to lockfile */
|
||||
write(m_pid_fd, str, strlen(str));
|
||||
}
|
||||
|
||||
return DR_CHILD;
|
||||
}
|
||||
|
||||
void CDaemon::finalise()
|
||||
{
|
||||
releaseLock(m_pid_fd, m_pidFileName);
|
||||
|
||||
if(!m_pidFileName.empty()) {
|
||||
unlink(m_pidFileName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int CDaemon::tryGetLock( const std::string& file )
|
||||
{
|
||||
mode_t m = umask( 0 );
|
||||
int fd = open( file.c_str(), O_RDWR|O_CREAT, 0640 );
|
||||
umask( m );
|
||||
if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) {
|
||||
close( fd );
|
||||
fd = -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
void CDaemon::releaseLock(int fd, const std::string& file)
|
||||
{
|
||||
if( fd < 0 )
|
||||
return;
|
||||
remove(file.c_str());
|
||||
close(fd);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 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 <string>
|
||||
|
||||
enum DAEMONIZE_RESULT
|
||||
{
|
||||
DR_PARENT,
|
||||
DR_CHILD,
|
||||
DR_FAILURE,
|
||||
DR_PIDFILE_FAILED
|
||||
};
|
||||
|
||||
class CDaemon
|
||||
{
|
||||
public:
|
||||
static DAEMONIZE_RESULT daemonise(const std::string& pidFile, const std::string& user);
|
||||
static void finalise();
|
||||
|
||||
private:
|
||||
static int tryGetLock(const std::string& file);
|
||||
static void releaseLock(int fd, const std::string& file);
|
||||
|
||||
static int m_pid_fd;
|
||||
static std::string m_pidFileName;
|
||||
};
|
||||
@ -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 <cerrno>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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 <netdb.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
};
|
||||
@ -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 <cassert>
|
||||
#include <string.h>
|
||||
#include <chrono>
|
||||
|
||||
// #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::milliseconds>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 <string>
|
||||
|
||||
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
|
||||
@ -0,0 +1,22 @@
|
||||
SRCS = $(wildcard *.cpp)
|
||||
OBJS = $(SRCS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d)
|
||||
|
||||
dgwtexttransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
|
||||
$(CC) $(CPPFLAGS) -o dgwtexttransmit $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
|
||||
|
||||
%.o : %.cpp
|
||||
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
|
||||
|
||||
.PHONY clean:
|
||||
clean:
|
||||
$(RM) *.o *.d dgwtexttransmit
|
||||
|
||||
.PHONY install:
|
||||
install: dgwtexttransmit
|
||||
# copy executable
|
||||
@cp -f dgwtexttransmit $(BIN_DIR)
|
||||
|
||||
../BaseCommon/BaseCommon.a:
|
||||
../DStarBase/DStarBase.a:
|
||||
../VersionInfo/GitVersion.h:
|
||||
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 <unordered_map>
|
||||
#include <vector>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include "SlowDataEncoder.h"
|
||||
#include "DStarDefines.h"
|
||||
#include "TextTransmit.h"
|
||||
#include "ProgramArgs.h"
|
||||
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
std::string repeater, text, filename;
|
||||
|
||||
if (!parseCLIArgs(argc, argv, repeater, text, filename)) {
|
||||
::fprintf(stderr, "dgwtexttransmit: invalid command line usage: dgwtexttransmit <repeater> -text <text>|-file <filename>, exiting\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
CHeaderData::initialise();
|
||||
|
||||
if (!filename.empty()) {
|
||||
std::ifstream file;
|
||||
file.open(filename);
|
||||
if (!file.is_open()) {
|
||||
::fprintf(stderr, "dgwtexttransmit: unable to open the file, exiting\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::getline(file, text);
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
text.resize(20U, ' ');
|
||||
|
||||
CTextTransmit tt(repeater, text);
|
||||
bool ret = tt.run();
|
||||
|
||||
return ret ? 0 : 1;
|
||||
}
|
||||
|
||||
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file)
|
||||
{
|
||||
repeater.clear();
|
||||
text.clear();
|
||||
file.clear();
|
||||
|
||||
if(argc < 4)
|
||||
return false;
|
||||
|
||||
std::unordered_map<std::string, std::string> namedArgs;
|
||||
std::vector<std::string> positionalArgs;
|
||||
|
||||
CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs);
|
||||
|
||||
if(namedArgs.count("text") == 0 && namedArgs.count("file") == 0)
|
||||
return false;
|
||||
|
||||
if(positionalArgs.size() != 1)
|
||||
return false;
|
||||
|
||||
repeater = boost::to_upper_copy(positionalArgs[0]);
|
||||
boost::replace_all(repeater, "_", " ");
|
||||
repeater.resize(LONG_CALLSIGN_LENGTH, ' ');
|
||||
|
||||
if(namedArgs.count("text") == 1) {
|
||||
text.assign(namedArgs["text"]);
|
||||
}
|
||||
else if(namedArgs.count("file") == 1){
|
||||
file.assign(namedArgs["file"]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CTextTransmit::CTextTransmit(const std::string& callsign, const std::string& text) :
|
||||
m_socket("", 0U),
|
||||
m_callsign(callsign),
|
||||
m_text(text)
|
||||
{
|
||||
}
|
||||
|
||||
CTextTransmit::~CTextTransmit()
|
||||
{
|
||||
}
|
||||
|
||||
bool CTextTransmit::run()
|
||||
{
|
||||
bool opened = m_socket.open();
|
||||
if (!opened)
|
||||
return false;
|
||||
|
||||
in_addr address = CUDPReaderWriter::lookup("127.0.0.1");
|
||||
|
||||
unsigned int id = CHeaderData::createId();
|
||||
|
||||
std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U);
|
||||
callsignG.push_back('G');
|
||||
|
||||
CHeaderData header;
|
||||
header.setId(id);
|
||||
header.setMyCall1(m_callsign);
|
||||
header.setMyCall2("INFO");
|
||||
header.setRptCall1(callsignG);
|
||||
header.setRptCall2(m_callsign);
|
||||
header.setYourCall("CQCQCQ ");
|
||||
header.setDestination(address, G2_DV_PORT);
|
||||
|
||||
sendHeader(header);
|
||||
|
||||
CSlowDataEncoder encoder;
|
||||
encoder.setHeaderData(header);
|
||||
encoder.setTextData(m_text);
|
||||
|
||||
CAMBEData data;
|
||||
data.setDestination(address, G2_DV_PORT);
|
||||
data.setId(id);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
unsigned int out = 0U;
|
||||
|
||||
for (;;) {
|
||||
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
|
||||
needed /= DSTAR_FRAME_TIME_MS;
|
||||
|
||||
while (out < needed) {
|
||||
data.setSeq(out);
|
||||
|
||||
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 (out == 0U) {
|
||||
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
|
||||
encoder.sync();
|
||||
} else {
|
||||
encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
|
||||
|
||||
sendData(data);
|
||||
out++;
|
||||
|
||||
if (out == 21U) {
|
||||
data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES);
|
||||
data.setSeq(0U);
|
||||
data.setEnd(true);
|
||||
|
||||
sendData(data);
|
||||
|
||||
m_socket.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10U));
|
||||
}
|
||||
}
|
||||
|
||||
bool CTextTransmit::sendHeader(const CHeaderData& header)
|
||||
{
|
||||
unsigned char buffer[60U];
|
||||
unsigned int length = header.getG2Data(buffer, 60U, true);
|
||||
|
||||
for (unsigned int i = 0U; i < 2U; i++) {
|
||||
bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort());
|
||||
if (!res)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CTextTransmit::sendData(const CAMBEData& data)
|
||||
{
|
||||
unsigned char buffer[40U];
|
||||
unsigned int length = data.getG2Data(buffer, 40U);
|
||||
|
||||
return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort());
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef TextTransmit_H
|
||||
#define TextTransmit_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "UDPReaderWriter.h"
|
||||
#include "HeaderData.h"
|
||||
#include "AMBEData.h"
|
||||
|
||||
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file);
|
||||
|
||||
class CTextTransmit {
|
||||
public:
|
||||
CTextTransmit(const std::string& callsign, const std::string& text);
|
||||
~CTextTransmit();
|
||||
|
||||
bool run();
|
||||
|
||||
private:
|
||||
CUDPReaderWriter m_socket;
|
||||
std::string m_callsign;
|
||||
std::string m_text;
|
||||
|
||||
bool sendHeader(const CHeaderData& header);
|
||||
bool sendData(const CAMBEData& data);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 <string>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <csignal>
|
||||
#ifdef DEBUG_DSTARGW
|
||||
#include <boost/stacktrace.hpp>
|
||||
#endif
|
||||
|
||||
#include "DGWTimeServerApp.h"
|
||||
#include "Version.h"
|
||||
#include "Log.h"
|
||||
#include "Daemon.h"
|
||||
#include "LogConsoleTarget.h"
|
||||
#include "LogFileTarget.h"
|
||||
|
||||
CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr;
|
||||
const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str());
|
||||
const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n";
|
||||
const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n";
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
std::set_terminate(CDGWTimeServerApp::terminateHandler);
|
||||
|
||||
signal(SIGSEGV, CDGWTimeServerApp::sigHandlerFatal);
|
||||
signal(SIGILL, CDGWTimeServerApp::sigHandlerFatal);
|
||||
signal(SIGFPE, CDGWTimeServerApp::sigHandlerFatal);
|
||||
signal(SIGABRT, CDGWTimeServerApp::sigHandlerFatal);
|
||||
signal(SIGTERM, CDGWTimeServerApp::sigHandler);
|
||||
signal(SIGINT, CDGWTimeServerApp::sigHandler);
|
||||
|
||||
if (2 != argc) {
|
||||
printf("usage: %s path_to_config_file\n", argv[0]);
|
||||
printf(" %s --version\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3;
|
||||
if(argv[1][0] == '-') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Load config
|
||||
std::string configfile(argv[1]);
|
||||
CTimeServerConfig config(configfile);
|
||||
if(!config.load())
|
||||
return 1;
|
||||
|
||||
// Do daemon stuff
|
||||
TDaemon daemon;
|
||||
config.getDameon(daemon);
|
||||
if (daemon.daemon) {
|
||||
CLog::logInfo("Configured as a daemon, detaching ...");
|
||||
auto res = CDaemon::daemonise(daemon.pidFile, daemon.user);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
case DR_PARENT:
|
||||
return 0;
|
||||
case DR_CHILD:
|
||||
break;
|
||||
case DR_PIDFILE_FAILED:
|
||||
case DR_FAILURE:
|
||||
default:
|
||||
CLog::logFatal("Failed to run as daemon");
|
||||
CLog::finalise();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Log
|
||||
TLog logConf;
|
||||
config.getLog(logConf);
|
||||
CLog::finalise();
|
||||
if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
|
||||
if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate));
|
||||
|
||||
// Start the app
|
||||
CDGWTimeServerApp app(&config);
|
||||
if(app.init()) {
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) :
|
||||
m_config(config)
|
||||
{
|
||||
g_app = this;
|
||||
assert(config != nullptr);
|
||||
}
|
||||
|
||||
CDGWTimeServerApp::~CDGWTimeServerApp()
|
||||
{
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
bool CDGWTimeServerApp::init()
|
||||
{
|
||||
return createThread();
|
||||
}
|
||||
|
||||
void CDGWTimeServerApp::run()
|
||||
{
|
||||
m_thread->Run();
|
||||
m_thread->Wait();
|
||||
}
|
||||
|
||||
bool CDGWTimeServerApp::createThread()
|
||||
{
|
||||
m_thread = new CTimeServerThread();
|
||||
|
||||
TTimeServer timeserver;
|
||||
m_config->getTimeServer(timeserver);
|
||||
|
||||
std::vector<std::string> rptrs = { "", "", "", "" };
|
||||
TRepeater repeater;
|
||||
for(unsigned int i = 0u; i < m_config->getRepeaterCount(); i++) {
|
||||
m_config->getRepeater(i, repeater);
|
||||
rptrs[i].assign(repeater.band);
|
||||
}
|
||||
|
||||
TPaths paths;
|
||||
m_config->getPaths(paths);
|
||||
|
||||
m_thread = new CTimeServerThread();
|
||||
bool ret = m_thread->setGateway(timeserver.callsign, rptrs[0], rptrs[1], rptrs[2], rptrs[3], timeserver.address, paths.data);
|
||||
if(ret) {
|
||||
m_thread->setAnnouncements(timeserver.language, timeserver.format, timeserver.interval);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CDGWTimeServerApp::sigHandler(int sig)
|
||||
{
|
||||
CLog::logInfo("Caught signal : %s, shutting down time server", strsignal(sig));
|
||||
|
||||
if(g_app != nullptr && g_app->m_thread != nullptr) {
|
||||
g_app->m_thread->kill();
|
||||
}
|
||||
}
|
||||
|
||||
void CDGWTimeServerApp::sigHandlerFatal(int sig)
|
||||
{
|
||||
CLog::logFatal("Caught signal : %s", strsignal(sig));
|
||||
fprintf(stderr, "Caught signal : %s\n", strsignal(sig));
|
||||
#ifdef DEBUG_DSTARGW
|
||||
std::stringstream stackTrace;
|
||||
stackTrace << boost::stacktrace::stacktrace();
|
||||
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
|
||||
fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str());
|
||||
#endif
|
||||
exit(3);
|
||||
}
|
||||
|
||||
void CDGWTimeServerApp::terminateHandler()
|
||||
{
|
||||
#ifdef DEBUG_DSTARGW
|
||||
std::stringstream stackTrace;
|
||||
stackTrace << boost::stacktrace::stacktrace();
|
||||
#endif
|
||||
|
||||
std::exception_ptr eptr;
|
||||
eptr = std::current_exception();
|
||||
|
||||
try {
|
||||
if (eptr != nullptr) {
|
||||
std::rethrow_exception(eptr);
|
||||
}
|
||||
else {
|
||||
CLog::logFatal("Unhandled unknown exception occured");
|
||||
fprintf(stderr, "Unknown ex\n");
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
CLog::logFatal("Unhandled exception occured %s", e.what());
|
||||
fprintf(stderr, "Unhandled ex %s\n", e.what());
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DSTARGW
|
||||
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
|
||||
#endif
|
||||
exit(2);
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 "TimeServerDefs.h"
|
||||
#include "TimeServerConfig.h"
|
||||
#include "TimeServerThread.h"
|
||||
|
||||
class CDGWTimeServerApp
|
||||
{
|
||||
public:
|
||||
CDGWTimeServerApp(const CTimeServerConfig * config);
|
||||
~CDGWTimeServerApp();
|
||||
|
||||
bool init();
|
||||
void run();
|
||||
|
||||
static void sigHandler(int sig);
|
||||
static void sigHandlerFatal(int sig);
|
||||
static void terminateHandler();
|
||||
|
||||
private:
|
||||
bool createThread();
|
||||
|
||||
static CDGWTimeServerApp * g_app;
|
||||
|
||||
const CTimeServerConfig * m_config;
|
||||
CTimeServerThread * m_thread;
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
SRCS = $(wildcard *.cpp)
|
||||
OBJS = $(SRCS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d)
|
||||
|
||||
dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
|
||||
$(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
|
||||
|
||||
%.o : %.cpp
|
||||
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@
|
||||
|
||||
.PHONY clean:
|
||||
clean:
|
||||
$(RM) *.o *.d dgwtimeserver
|
||||
|
||||
.PHONY install:
|
||||
install: dgwtimeserver
|
||||
# copy executable
|
||||
@cp -f dgwtimeserver $(BIN_DIR)
|
||||
|
||||
# copy and adjust config
|
||||
@cp -fn example.cfg $(CFG_DIR)/dgwtimeserver.cfg
|
||||
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
|
||||
@sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
|
||||
|
||||
# SystemD service install
|
||||
@cp -f ../debian/dgwtimeserver.service /lib/systemd/system/
|
||||
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dgwtimeserver.service
|
||||
systemctl enable dgwtimeserver.service
|
||||
@systemctl daemon-reload
|
||||
@echo "\n"
|
||||
@echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'"
|
||||
@echo "\n"
|
||||
|
||||
../BaseCommon/BaseCommon.a:
|
||||
../DStarBase/DStarBase.a:
|
||||
../VersionInfo/GitVersion.h:
|
||||
@ -0,0 +1,11 @@
|
||||
Time Server for DStarGateway. Installs as a systemd service.
|
||||
|
||||
If you did not change build settings, the configuration can be found in
|
||||
```
|
||||
/usr/local/etc/dgwtimeserver.cfg
|
||||
```
|
||||
After configuring enable and start the dgwtimeserver systemd service using :
|
||||
```
|
||||
sudo systemctl enable dgwtimeserver
|
||||
sudo systemctl start dgwtimeserver
|
||||
```
|
||||
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 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 <algorithm>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "TimeServerConfig.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
CTimeServerConfig::CTimeServerConfig(const std::string &pathname) :
|
||||
m_fileName(pathname),
|
||||
m_repeaters()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CTimeServerConfig::~CTimeServerConfig()
|
||||
{
|
||||
for(auto repeater : m_repeaters) {
|
||||
delete repeater;
|
||||
}
|
||||
m_repeaters.clear();
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::load()
|
||||
{
|
||||
bool ret = false;
|
||||
CLog::logInfo("Loading Configuration from %s", m_fileName.c_str());
|
||||
CConfig cfg(m_fileName);
|
||||
|
||||
ret = open(cfg);
|
||||
if(ret) {
|
||||
ret = loadTimeServer(cfg) && ret;
|
||||
ret = loadRepeaters(cfg) && ret;
|
||||
ret = loadDaemon(cfg) && ret;
|
||||
ret = loadPaths(cfg) && ret;
|
||||
ret = loadLog(cfg) && ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::open(CConfig & cfg)
|
||||
{
|
||||
try {
|
||||
return cfg.load();
|
||||
}
|
||||
catch(...) {
|
||||
CLog::logError("Can't read %s\n", m_fileName.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::loadRepeaters(const CConfig & cfg)
|
||||
{
|
||||
m_repeaters.clear();
|
||||
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
|
||||
bool repeaterEnabled;
|
||||
|
||||
bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false);
|
||||
if(!ret || !repeaterEnabled)
|
||||
continue;
|
||||
|
||||
TRepeater * repeater = new TRepeater;
|
||||
ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret;
|
||||
if(!ret) {
|
||||
delete repeater;
|
||||
continue;
|
||||
}
|
||||
boost::to_upper(repeater->band);
|
||||
|
||||
bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;});
|
||||
if(alreadyConfigured) {
|
||||
CLog::logWarning("%s-%s repeater already configured, ignoring", m_timeServer.callsign.c_str(), repeater->band.c_str());
|
||||
delete repeater;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_repeaters.push_back(repeater);
|
||||
}
|
||||
|
||||
return m_repeaters.size() > 0U;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::loadTimeServer(const CConfig & cfg)
|
||||
{
|
||||
bool ret = cfg.getValue("timeserver", "callsign", m_timeServer.callsign, 3, 8, "");
|
||||
boost::to_upper(m_timeServer.callsign);
|
||||
ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret;
|
||||
|
||||
std::string format;
|
||||
ret = cfg.getValue("timeserver", "format", format, "voice", {"voice", "text" }) && ret;
|
||||
if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME;
|
||||
else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME;
|
||||
|
||||
std::string lang;
|
||||
ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;;
|
||||
if (lang == "english_uk_1") m_timeServer.language = LANG_ENGLISH_UK_1;
|
||||
else if(lang == "english_uk_2") m_timeServer.language = LANG_ENGLISH_UK_2;
|
||||
else if(lang == "english_us_1") m_timeServer.language = LANG_ENGLISH_US_1;
|
||||
else if(lang == "english_us_2") m_timeServer.language = LANG_ENGLISH_US_2;
|
||||
else if(lang == "deutsch_1" ) m_timeServer.language = LANG_DEUTSCH_1;
|
||||
else if(lang == "detusch_2" ) m_timeServer.language = LANG_DEUTSCH_2;
|
||||
else if(lang == "francais" ) m_timeServer.language = LANG_FRANCAIS;
|
||||
else if(lang == "nederlands" ) m_timeServer.language = LANG_NEDERLANDS;
|
||||
else if(lang == "svenska" ) m_timeServer.language = LANG_SVENSKA;
|
||||
else if(lang == "espanol" ) m_timeServer.language = LANG_ESPANOL;
|
||||
else if(lang == "norsk" ) m_timeServer.language = LANG_NORSK;
|
||||
else if(lang == "portugues" ) m_timeServer.language = LANG_PORTUGUES;
|
||||
|
||||
std::string interval;
|
||||
ret = cfg.getValue("timeserver", "interval", interval, "30", {"15", "30", "60"}) && ret;
|
||||
if(interval == "15") m_timeServer.interval = INTERVAL_15MINS;
|
||||
else if(interval == "30") m_timeServer.interval = INTERVAL_30MINS;
|
||||
else if(interval == "60") m_timeServer.interval = INTERVAL_60MINS;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::loadDaemon(const CConfig & cfg)
|
||||
{
|
||||
bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false);
|
||||
ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret;
|
||||
ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::loadPaths(const CConfig & cfg)
|
||||
{
|
||||
bool ret = cfg.getValue("paths", "data", m_paths.data, 1, 1024, "");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CTimeServerConfig::loadLog(const CConfig & cfg)
|
||||
{
|
||||
bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/");
|
||||
if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
|
||||
m_log.logDir.push_back('/');
|
||||
}
|
||||
|
||||
ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dgwtimeserver") && ret;
|
||||
ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
|
||||
|
||||
std::string levelStr;
|
||||
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
|
||||
if(ret) {
|
||||
if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
|
||||
else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
|
||||
else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
|
||||
else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
|
||||
else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
|
||||
else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
|
||||
else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
|
||||
}
|
||||
|
||||
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
|
||||
if(ret) {
|
||||
if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
|
||||
else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
|
||||
else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
|
||||
else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
|
||||
else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
|
||||
else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
|
||||
else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const
|
||||
{
|
||||
timeserver = m_timeServer;
|
||||
}
|
||||
|
||||
void CTimeServerConfig::getDameon(TDaemon& daemon) const
|
||||
{
|
||||
daemon = m_daemon;
|
||||
}
|
||||
|
||||
unsigned int CTimeServerConfig::getRepeaterCount() const
|
||||
{
|
||||
return m_repeaters.size();
|
||||
}
|
||||
|
||||
void CTimeServerConfig::getRepeater(unsigned int idx, TRepeater& repeater) const
|
||||
{
|
||||
repeater = *(m_repeaters[idx]);
|
||||
}
|
||||
|
||||
void CTimeServerConfig::getPaths(TPaths& paths) const
|
||||
{
|
||||
paths = m_paths;
|
||||
}
|
||||
|
||||
void CTimeServerConfig::getLog(TLog& log) const
|
||||
{
|
||||
log = m_log;
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 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 <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Config.h"
|
||||
#include "TimeServerDefs.h"
|
||||
#include "Log.h"
|
||||
|
||||
typedef struct {
|
||||
std::string callsign;
|
||||
std::string address;
|
||||
FORMAT format;
|
||||
LANGUAGE language;
|
||||
INTERVAL interval;
|
||||
} TTimeServer;
|
||||
|
||||
typedef struct {
|
||||
std::string band;
|
||||
} TRepeater;
|
||||
|
||||
typedef struct {
|
||||
bool daemon;
|
||||
std::string pidFile;
|
||||
std::string user;
|
||||
} TDaemon;
|
||||
|
||||
typedef struct {
|
||||
std::string data;
|
||||
} TPaths;
|
||||
|
||||
typedef struct {
|
||||
std::string logDir;
|
||||
LOG_SEVERITY displayLevel;
|
||||
LOG_SEVERITY fileLevel;
|
||||
std::string fileRoot;
|
||||
bool fileRotate;
|
||||
} TLog;
|
||||
|
||||
class CTimeServerConfig
|
||||
{
|
||||
public:
|
||||
CTimeServerConfig(const std::string &pathname);
|
||||
~CTimeServerConfig();
|
||||
|
||||
bool load();
|
||||
void getTimeServer(TTimeServer& timeserver) const;
|
||||
void getDameon(TDaemon& daemon) const;
|
||||
unsigned int getRepeaterCount() const;
|
||||
void getRepeater(unsigned int idx, TRepeater& repeater) const;
|
||||
void getPaths(TPaths& paths) const;
|
||||
void getLog(TLog& log) const;
|
||||
|
||||
private:
|
||||
bool open(CConfig & cfg);
|
||||
bool loadRepeaters(const CConfig & cfg);
|
||||
bool loadTimeServer(const CConfig & cfg);
|
||||
bool loadDaemon(const CConfig & cfg);
|
||||
bool loadPaths(const CConfig & cfg);
|
||||
bool loadLog(const CConfig & cfg);
|
||||
|
||||
std::string m_fileName;
|
||||
std::vector<TRepeater *> m_repeaters;
|
||||
TTimeServer m_timeServer;
|
||||
TDaemon m_daemon;
|
||||
TPaths m_paths;
|
||||
TLog m_log;
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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
|
||||
|
||||
const std::string APPLICATION_NAME("DStarGateway Time Server");
|
||||
|
||||
enum LANGUAGE {
|
||||
LANG_ENGLISH_UK_1,
|
||||
LANG_ENGLISH_UK_2,
|
||||
LANG_ENGLISH_US_1,
|
||||
LANG_ENGLISH_US_2,
|
||||
LANG_DEUTSCH_1,
|
||||
LANG_DEUTSCH_2,
|
||||
LANG_FRANCAIS,
|
||||
LANG_NEDERLANDS,
|
||||
LANG_SVENSKA,
|
||||
LANG_ESPANOL,
|
||||
LANG_NORSK,
|
||||
LANG_PORTUGUES
|
||||
};
|
||||
|
||||
enum INTERVAL {
|
||||
INTERVAL_15MINS,
|
||||
INTERVAL_30MINS,
|
||||
INTERVAL_60MINS
|
||||
};
|
||||
|
||||
enum FORMAT {
|
||||
FORMAT_VOICE_TIME,
|
||||
FORMAT_TEXT_TIME
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2012,2013 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 <unordered_map>
|
||||
|
||||
#include "SlowDataEncoder.h"
|
||||
#include "UDPReaderWriter.h"
|
||||
#include "TimeServerDefs.h"
|
||||
#include "HeaderData.h"
|
||||
#include "AMBEData.h"
|
||||
#include "Thread.h"
|
||||
#include "AMBEFileReader.h"
|
||||
|
||||
class CTimeServerThread : public CThread
|
||||
{
|
||||
public:
|
||||
CTimeServerThread();
|
||||
~CTimeServerThread();
|
||||
|
||||
bool setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath);
|
||||
void setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval);
|
||||
|
||||
void * Entry();
|
||||
void kill();
|
||||
|
||||
private:
|
||||
std::string m_callsign;
|
||||
std::vector<std::string> m_repeaters;
|
||||
std::string m_callsignG;
|
||||
in_addr m_address;
|
||||
std::string m_addressStr;
|
||||
LANGUAGE m_language;
|
||||
FORMAT m_format;
|
||||
INTERVAL m_interval;
|
||||
CSlowDataEncoder m_encoder;
|
||||
std::vector<CAMBEData*> m_data;
|
||||
bool m_killed;
|
||||
std::string m_dataPath;
|
||||
CAMBEFileReader * m_ambeFileReader;
|
||||
|
||||
void sendTime(unsigned int hour, unsigned int min);
|
||||
|
||||
std::vector<std::string> sendTimeEnGB1(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeEnGB2(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeEnUS1(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeEnUS2(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeDeDE1(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeDeDE2(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeFrFR(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeNlNL(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeSeSE(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeEsES(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimeNoNO(unsigned int hour, unsigned int min);
|
||||
std::vector<std::string> sendTimePtPT(unsigned int hour, unsigned int min);
|
||||
|
||||
bool send(const std::vector<std::string>& words, unsigned int hour, unsigned int min);
|
||||
bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header);
|
||||
bool sendData(CUDPReaderWriter& socket, const CAMBEData& data);
|
||||
|
||||
bool loadAMBE();
|
||||
|
||||
bool lookup(const std::string& id);
|
||||
|
||||
void buildAudio(const std::vector<std::string>& words, CSlowDataEncoder& slowDataEncoder);
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
[TimeServer]
|
||||
callsign= # call of the gateway to send time beacons without G letter
|
||||
address= # address of the gateway, defaults to 127.0.0.1
|
||||
format= # possible values are voice, text, defaults to voice. note that voice also sends text along.
|
||||
language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1
|
||||
interval= # valid values are 15, 30 and 60, defaults to 30
|
||||
|
||||
[Paths]
|
||||
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found
|
||||
|
||||
# Up to 4 repeaters can be enabled to transmit time beacons
|
||||
[Repeater_1]
|
||||
enabled=true # enable time beacons on this repeater
|
||||
band=B # Module letter of the repeater
|
||||
|
||||
[Repeater_2]
|
||||
enabled=false # enable time beacons on this repeater
|
||||
band= # Module letter of the repeater
|
||||
|
||||
[Repeater_3]
|
||||
enabled=false # enable time beacons on this repeater
|
||||
band= # Module letter of the repeater
|
||||
|
||||
[Repeater_4]
|
||||
enabled=false # enable time beacons on this repeater
|
||||
band= # Module letter of the repeater
|
||||
|
||||
[Log]
|
||||
path=/var/log/dstargateway/
|
||||
fileRoot= # defaults to dgwtimeserver
|
||||
fileRotate= # rotate log files daily, defaults to true
|
||||
fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
|
||||
displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
|
||||
|
||||
# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd
|
||||
# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might
|
||||
# run the program using sysv or any other old school init system.
|
||||
[Daemon]
|
||||
daemon=false
|
||||
pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice
|
||||
user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root
|
||||
@ -0,0 +1,23 @@
|
||||
SRCS = $(wildcard *.cpp)
|
||||
OBJS = $(SRCS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d)
|
||||
|
||||
dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a
|
||||
$(CC) $(CPPFLAGS) -o dgwvoicetransmit $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
|
||||
|
||||
%.o : %.cpp
|
||||
$(CC) -I../BaseCommon -I../APRS -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
|
||||
|
||||
.PHONY clean:
|
||||
clean:
|
||||
$(RM) *.o *.d dgwvoicetransmit
|
||||
|
||||
.PHONY install:
|
||||
install: dgwvoicetransmit
|
||||
# copy executable
|
||||
@cp -f dgwvoicetransmit $(BIN_DIR)
|
||||
|
||||
../APRS/APRS.a:
|
||||
../BaseCommon/BaseCommon.a:
|
||||
../DStarBase/DStarBase.a:
|
||||
../VersionInfo/GitVersion.h:
|
||||
@ -0,0 +1,31 @@
|
||||
|
||||
DGWVoiceTransmit allow you to transmit .dvtool files through your repeater.
|
||||
It must be run on the same machine where DStarGateway is running.
|
||||
|
||||
- [1. Usage Examples](#1-usage-examples)
|
||||
- [1.1. Transmit one or more files](#11-transmit-one-or-more-files)
|
||||
- [1.2. Override or insert Text Data](#12-override-or-insert-text-data)
|
||||
- [1.3. Override or insert DPRS Data](#13-override-or-insert-dprs-data)
|
||||
|
||||
# 1. Usage Examples
|
||||
Here are a few usage examples and explanation
|
||||
## 1.1. Transmit one or more files
|
||||
This wil transmit one file after the other tthoug repeater N0CALL B. You can either specify the call sign using spaces or underscores. When using spaces, make sure you put it into quotes.
|
||||
```
|
||||
dgwvoicetransmit N0CALL_B file1.dvtool file2.dvtool
|
||||
dgwvoicetransmit "N0CALL B" file1.dvtool file2.dvtool
|
||||
```
|
||||
## 1.2. Override or insert Text Data
|
||||
It is possible to ovveride the text slow data contained in the .dvtool file. For this, use -text flag. If no text data is present, the specified text data will be inserted into the transmsssion
|
||||
```
|
||||
dgwvoicetransmit N0CALL_B -text "Override text data" file1.dvtool
|
||||
```
|
||||
|
||||
## 1.3. Override or insert DPRS Data
|
||||
You cann also override or insert DPRS data. This will cause modern DStar Radio to display a popup with distance, bearing and other DPRS information etc.
|
||||
|
||||
You have to insert an "Icom compatible" DPRS frame. No validatin is made wether the frame is valid or not.
|
||||
```
|
||||
dgwvoicetransmit N0CALL_B -dprs "!4898.03N/00844.64Er/DPRS Comment" file1.dvtool
|
||||
This can be combined with -text flag.
|
||||
```
|
||||
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 "DStarDefines.h"
|
||||
#include "VoiceStore.h"
|
||||
|
||||
CVoiceStore::CVoiceStore(const std::vector<std::string>& filenames) :
|
||||
m_filenames(),
|
||||
m_header(NULL),
|
||||
m_fileNumber(0U),
|
||||
m_file()
|
||||
{
|
||||
for(auto f : filenames) {
|
||||
m_filenames.push_back(f);
|
||||
}
|
||||
}
|
||||
|
||||
CVoiceStore::~CVoiceStore()
|
||||
{
|
||||
m_filenames.clear();
|
||||
}
|
||||
|
||||
bool CVoiceStore::open()
|
||||
{
|
||||
std::string fileName = m_filenames.at(0U);
|
||||
|
||||
bool ret = m_file.open(fileName);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
DVTFR_TYPE type = m_file.read();
|
||||
if (type != DVTFR_HEADER) {
|
||||
m_file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_header = m_file.readHeader();
|
||||
m_fileNumber = 0U;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CHeaderData* CVoiceStore::getHeader()
|
||||
{
|
||||
CHeaderData* header = m_header;
|
||||
|
||||
m_header = NULL;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
CAMBEData* CVoiceStore::getAMBE()
|
||||
{
|
||||
DVTFR_TYPE type = m_file.read();
|
||||
if (type == DVTFR_DATA) {
|
||||
CAMBEData* ambe = m_file.readData();
|
||||
if (ambe != NULL)
|
||||
return ambe;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
m_file.close();
|
||||
m_fileNumber++;
|
||||
|
||||
// The end of the last file?
|
||||
if (m_fileNumber == m_filenames.size())
|
||||
return NULL;
|
||||
|
||||
std::string filename = m_filenames.at(m_fileNumber);
|
||||
|
||||
bool ret = m_file.open(filename);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
// This should get the header
|
||||
type = m_file.read();
|
||||
|
||||
if (type == DVTFR_HEADER) {
|
||||
// Throw the header away
|
||||
CHeaderData* header = m_file.readHeader();
|
||||
delete header;
|
||||
} else if (type == DVTFR_DATA) {
|
||||
// Shouldn't usually happen
|
||||
CAMBEData* ambe = m_file.readData();
|
||||
if (ambe != NULL)
|
||||
return ambe;
|
||||
else
|
||||
continue;
|
||||
} else {
|
||||
// !!!!
|
||||
continue;
|
||||
}
|
||||
|
||||
// This should get the first data record
|
||||
type = m_file.read();
|
||||
|
||||
if (type == DVTFR_DATA) {
|
||||
CAMBEData* ambe = m_file.readData();
|
||||
if (ambe != NULL)
|
||||
return ambe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CVoiceStore::close()
|
||||
{
|
||||
m_file.close();
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef VoiceStore_H
|
||||
#define VoiceStore_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "DVTOOLFileReader.h"
|
||||
#include "HeaderData.h"
|
||||
#include "AMBEData.h"
|
||||
|
||||
|
||||
class CVoiceStore {
|
||||
public:
|
||||
CVoiceStore(const std::vector<std::string>& filenames);
|
||||
~CVoiceStore();
|
||||
|
||||
bool open();
|
||||
|
||||
CHeaderData* getHeader();
|
||||
|
||||
CAMBEData* getAMBE();
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_filenames;
|
||||
CHeaderData* m_header;
|
||||
unsigned int m_fileNumber;
|
||||
CDVTOOLFileReader m_file;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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 <cassert>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include "ProgramArgs.h"
|
||||
#include "DStarDefines.h"
|
||||
#include "VoiceTransmit.h"
|
||||
#include "SlowDataEncoder.h"
|
||||
#include "APRSUtils.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
std::string repeater, text, dprs;
|
||||
std::vector<std::string> filenames;
|
||||
|
||||
if (!parseCLIArgs(argc, argv, repeater, filenames, text, dprs)) {
|
||||
::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] [-dprs dprs] <repeater> <file1> <file2> ..., exiting\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
CHeaderData::initialise();
|
||||
|
||||
CVoiceStore store(filenames);
|
||||
bool opened = store.open();
|
||||
if (!opened) {
|
||||
::fprintf(stderr, "dgwvoicetransmit: unable to open one of the files, exiting\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
CVoiceTransmit tt(repeater, &store, text, dprs);
|
||||
bool ret = tt.run();
|
||||
|
||||
store.close();
|
||||
|
||||
|
||||
return ret ? 0 : 1;
|
||||
}
|
||||
|
||||
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector<std::string>& files, std::string& text, std::string& dprs)
|
||||
{
|
||||
if(argc < 3)
|
||||
return false;
|
||||
|
||||
std::unordered_map<std::string, std::string> namedArgs;
|
||||
std::vector<std::string> positionalArgs;
|
||||
|
||||
CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs);
|
||||
|
||||
if(positionalArgs.size() < 2U)
|
||||
return false;
|
||||
|
||||
repeater.assign(boost::replace_all_copy(boost::to_upper_copy(positionalArgs[0]), "_", " "));
|
||||
files.assign(positionalArgs.begin() + 1, positionalArgs.end());
|
||||
|
||||
if(namedArgs.count("text") > 0U) {
|
||||
text.assign(namedArgs["text"]);
|
||||
}
|
||||
else {
|
||||
text.assign("");
|
||||
}
|
||||
|
||||
if(namedArgs.count("dprs") > 0U) {
|
||||
std::string dprsRepeater(repeater);
|
||||
CAPRSUtils::dstarCallsignToAPRS(dprsRepeater);
|
||||
std::string dprsnoCrc = CStringUtils::string_format("%s>DPRS:%s\r", dprsRepeater.c_str(), namedArgs["dprs"].c_str());
|
||||
dprs = CStringUtils::string_format("$$CRC%04X,%s", CAPRSUtils::calcGPSAIcomCRC(dprsnoCrc), dprsnoCrc.c_str());
|
||||
}
|
||||
else {
|
||||
dprs.assign("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs) :
|
||||
m_socket("", 0U),
|
||||
m_callsign(callsign),
|
||||
m_text(text),
|
||||
m_dprs(dprs),
|
||||
m_store(store)
|
||||
{
|
||||
assert(store != NULL);
|
||||
}
|
||||
|
||||
CVoiceTransmit::~CVoiceTransmit()
|
||||
{
|
||||
}
|
||||
|
||||
bool CVoiceTransmit::run()
|
||||
{
|
||||
CSlowDataEncoder * slowData = nullptr;
|
||||
bool opened = m_socket.open();
|
||||
if (!opened)
|
||||
return false;
|
||||
|
||||
in_addr address = CUDPReaderWriter::lookup("127.0.0.1");
|
||||
|
||||
unsigned int id = CHeaderData::createId();
|
||||
|
||||
std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U);
|
||||
callsignG.push_back('G');
|
||||
|
||||
CHeaderData* header = m_store->getHeader();
|
||||
if (header == NULL) {
|
||||
m_socket.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
header->setId(id);
|
||||
header->setRptCall1(callsignG);
|
||||
header->setRptCall2(m_callsign);
|
||||
header->setDestination(address, G2_DV_PORT);
|
||||
|
||||
if(!m_text.empty()) {
|
||||
slowData = new CSlowDataEncoder();
|
||||
// slowData->setHeaderData(*header);
|
||||
if(!m_text.empty()) slowData->setTextData(m_text);
|
||||
if(!m_dprs.empty()) slowData->setGPSData(m_dprs);
|
||||
}
|
||||
|
||||
sendHeader(header);
|
||||
|
||||
delete header;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
unsigned int out = 0U;
|
||||
unsigned int seqNo = 0U;
|
||||
bool loop = true;
|
||||
|
||||
while (loop) {
|
||||
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
|
||||
needed /= DSTAR_FRAME_TIME_MS;
|
||||
|
||||
while (out < needed) {
|
||||
CAMBEData* ambe = m_store->getAMBE();
|
||||
|
||||
if (ambe == NULL) {
|
||||
CAMBEData data;
|
||||
data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES);
|
||||
data.setDestination(address, G2_DV_PORT);
|
||||
data.setId(id);
|
||||
data.setSeq(seqNo);
|
||||
data.setEnd(true);
|
||||
|
||||
sendData(&data);
|
||||
|
||||
m_socket.close();
|
||||
|
||||
loop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(slowData != nullptr) { // Override slowdata if specified so
|
||||
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
||||
ambe->getData(buffer, DV_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);
|
||||
} else {
|
||||
slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
ambe->setData(buffer, DV_FRAME_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
ambe->setSeq(seqNo);
|
||||
ambe->setDestination(address, G2_DV_PORT);
|
||||
ambe->setEnd(false);
|
||||
ambe->setId(id);
|
||||
|
||||
sendData(ambe);
|
||||
delete ambe;
|
||||
|
||||
out++;
|
||||
seqNo++;
|
||||
if(seqNo >= 21U) seqNo = 0U;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10U));
|
||||
}
|
||||
|
||||
if(slowData != nullptr) delete slowData;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVoiceTransmit::sendHeader(CHeaderData* header)
|
||||
{
|
||||
assert(header != NULL);
|
||||
|
||||
unsigned char buffer[60U];
|
||||
unsigned int length = header->getG2Data(buffer, 60U, true);
|
||||
|
||||
for (unsigned int i = 0U; i < 2U; i++) {
|
||||
bool res = m_socket.write(buffer, length, header->getYourAddress(), header->getYourPort());
|
||||
if (!res)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVoiceTransmit::sendData(CAMBEData* data)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
unsigned char buffer[40U];
|
||||
unsigned int length = data->getG2Data(buffer, 40U);
|
||||
|
||||
return m_socket.write(buffer, length, data->getYourAddress(), data->getYourPort());
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef VoiceTransmit_H
|
||||
#define VoiceTransmit_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "UDPReaderWriter.h"
|
||||
#include "VoiceStore.h"
|
||||
#include "HeaderData.h"
|
||||
#include "AMBEData.h"
|
||||
|
||||
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector<std::string>& vector, std::string& text, std::string& dprs);
|
||||
|
||||
class CVoiceTransmit {
|
||||
public:
|
||||
CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs);
|
||||
~CVoiceTransmit();
|
||||
|
||||
bool run();
|
||||
|
||||
private:
|
||||
CUDPReaderWriter m_socket;
|
||||
std::string m_callsign;
|
||||
std::string m_text;
|
||||
std::string m_dprs;
|
||||
CVoiceStore* m_store;
|
||||
|
||||
bool sendHeader(CHeaderData* header);
|
||||
bool sendData(CAMBEData* data);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <sys/stat.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "AMBEFileReader.h"
|
||||
#include "DStarDefines.h"
|
||||
#include "Log.h"
|
||||
|
||||
const unsigned int SILENCE_LENGTH = 10U;
|
||||
|
||||
CAMBEFileReader::CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile) :
|
||||
m_indexFile(indexFile),
|
||||
m_ambeFile(ambeFile),
|
||||
m_ambe(nullptr),
|
||||
m_ambeLength(0U),
|
||||
m_index()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CAMBEFileReader::~CAMBEFileReader()
|
||||
{
|
||||
if(m_ambe != nullptr) {
|
||||
delete[] m_ambe;
|
||||
}
|
||||
}
|
||||
|
||||
bool CAMBEFileReader::read()
|
||||
{
|
||||
bool ret = readAmbe() && readIndex();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CAMBEFileReader::readAmbe()
|
||||
{
|
||||
struct stat sbuf;
|
||||
if (stat(m_ambeFile.c_str(), &sbuf)) {
|
||||
CLog::logWarning("File %s not readable\n", m_ambeFile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int fsize = sbuf.st_size;
|
||||
|
||||
FILE *file = fopen(m_ambeFile.c_str(), "rb");
|
||||
if (NULL == file) {
|
||||
CLog::logError("Cannot open %s for reading\n", m_ambeFile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CLog::logInfo("Reading %s\n", m_ambeFile.c_str());
|
||||
|
||||
unsigned char buffer[VOICE_FRAME_LENGTH_BYTES];
|
||||
|
||||
size_t n = fread(buffer, sizeof(unsigned char), 4, file);
|
||||
if (n != 4) {
|
||||
CLog::logError("Unable to read the header from %s\n", m_ambeFile.c_str());
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(buffer, "AMBE", 4)) {
|
||||
CLog::logError("Invalid header from %s\n", m_ambeFile.c_str());
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Length of the file minus the header
|
||||
unsigned int length = fsize - 4U;
|
||||
|
||||
// Hold the file data plus silence at the end
|
||||
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES];
|
||||
m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES;
|
||||
|
||||
// Add silence to the beginning of the buffer
|
||||
unsigned char* p = m_ambe;
|
||||
for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES)
|
||||
memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
|
||||
|
||||
n = fread(p, 1, length, file);
|
||||
if (n != length) {
|
||||
CLog::logError("Unable to read the AMBE data from %s\n", m_ambeFile.c_str());
|
||||
fclose(file);
|
||||
delete[] m_ambe;
|
||||
m_ambeLength = 0U;
|
||||
m_ambe = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAMBEFileReader::readIndex()
|
||||
{
|
||||
struct stat sbuf;
|
||||
|
||||
if (stat(m_indexFile.c_str(), &sbuf)) {
|
||||
CLog::logError("File %s not readable\n", m_indexFile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *file = fopen(m_indexFile.c_str(), "r");
|
||||
if (file == nullptr) {
|
||||
CLog::logError("Cannot open %s for reading\n", m_indexFile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add a silence entry at the beginning
|
||||
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
|
||||
|
||||
CLog::logInfo("Reading %s\n", m_indexFile.c_str());
|
||||
|
||||
char line[128];
|
||||
while (fgets(line, 128, file)) {
|
||||
if (strlen(line) && '#'!=line[0]) {
|
||||
const std::string space(" \t\r\n");
|
||||
std::string name(strtok(line, space.c_str()));
|
||||
std::string strt(strtok(NULL, space.c_str()));
|
||||
std::string leng(strtok(NULL, space.c_str()));
|
||||
|
||||
if (name.size() && strt.size() && leng.size()) {
|
||||
unsigned long start = std::stoul(strt);
|
||||
unsigned long length = std::stoul(leng);
|
||||
|
||||
if (start >= m_ambeLength || (start + length) >= m_ambeLength)
|
||||
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length);
|
||||
else
|
||||
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAMBEFileReader::lookup(const std::string &id, std::vector<CAMBEData *>& data)
|
||||
{
|
||||
if(m_index.count(id) == 0U) {
|
||||
CLog::logError("Cannot find the AMBE index for *%s*", id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CIndexRecord* info = m_index[id];
|
||||
unsigned int start = info->getStart();
|
||||
unsigned int length = info->getLength();
|
||||
|
||||
for (unsigned int i = 0U; i < length; i++) {
|
||||
unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES;
|
||||
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
||||
::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES);
|
||||
|
||||
CAMBEData* dataOut = new CAMBEData;
|
||||
dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES);
|
||||
data.push_back(dataOut);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "AMBEData.h"
|
||||
|
||||
class CIndexRecord {
|
||||
public:
|
||||
CIndexRecord(const std::string& name, unsigned int start, unsigned int length) :
|
||||
m_name(name),
|
||||
m_start(start),
|
||||
m_length(length)
|
||||
{
|
||||
}
|
||||
|
||||
std::string getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
unsigned int getStart() const
|
||||
{
|
||||
return m_start;
|
||||
}
|
||||
|
||||
unsigned int getLength() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
unsigned int m_start;
|
||||
unsigned int m_length;
|
||||
};
|
||||
|
||||
class CAMBEFileReader
|
||||
{
|
||||
public:
|
||||
CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile);
|
||||
~CAMBEFileReader();
|
||||
bool read();
|
||||
bool lookup(const std::string &id, std::vector<CAMBEData *>& data);
|
||||
|
||||
private:
|
||||
bool readAmbe();
|
||||
bool readIndex();
|
||||
|
||||
std::string m_indexFile;
|
||||
std::string m_ambeFile;
|
||||
unsigned char* m_ambe;
|
||||
unsigned int m_ambeLength;
|
||||
std::unordered_map<std::string, CIndexRecord*> m_index;
|
||||
};
|
||||
Binary file not shown.
@ -0,0 +1,44 @@
|
||||
0 94 29
|
||||
1 140 16
|
||||
2 173 20
|
||||
3 215 19
|
||||
4 255 24
|
||||
5 298 29
|
||||
6 344 34
|
||||
7 398 25
|
||||
8 440 24
|
||||
9 481 23
|
||||
alpha 521 29
|
||||
bravo 568 27
|
||||
charlie 615 30
|
||||
delta 666 30
|
||||
A 714 21
|
||||
B 753 27
|
||||
C 796 34
|
||||
D 848 30
|
||||
E 895 23
|
||||
F 936 27
|
||||
G 982 27
|
||||
H 1026 30
|
||||
I 1075 20
|
||||
J 1113 29
|
||||
K 1162 22
|
||||
L 1201 28
|
||||
M 1248 28
|
||||
N 1295 28
|
||||
O 1341 23
|
||||
P 1386 21
|
||||
Q 1426 23
|
||||
R 1466 28
|
||||
S 1511 31
|
||||
T 1567 21
|
||||
U 1606 22
|
||||
V 1646 30
|
||||
W 1693 46
|
||||
X 1756 31
|
||||
Y 1806 21
|
||||
Z 1844 14
|
||||
linkedto 1858 35
|
||||
notlinked 1941 46
|
||||
linkingto 2008 38
|
||||
isbusy 2091 47
|
||||
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <gtest/gtest.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "AMBEFileReader.h"
|
||||
#include "AMBEData.h"
|
||||
|
||||
namespace AMBEFileReaderTests
|
||||
{
|
||||
class AMBEFileReader_lookup : public ::testing::Test {
|
||||
|
||||
};
|
||||
|
||||
TEST_F(AMBEFileReader_lookup, nonExistentFiles)
|
||||
{
|
||||
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||
|
||||
std::vector<CAMBEData *> data;
|
||||
res = reader.lookup("0", data);
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||
|
||||
for(auto d : data) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_lookup, onlyIndexFileExists)
|
||||
{
|
||||
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
|
||||
std::vector<CAMBEData *> data;
|
||||
res = reader.lookup("0", data);
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
|
||||
for(auto d : data) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists)
|
||||
{
|
||||
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
|
||||
std::vector<CAMBEData *> data;
|
||||
res = reader.lookup("0", data);
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
|
||||
for(auto d : data) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_lookup, validId)
|
||||
{
|
||||
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||
CAMBEFileReader reader(indexFile, ambeFile);
|
||||
bool res = reader.read();
|
||||
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||
|
||||
std::vector<CAMBEData *> data;
|
||||
res = reader.lookup("0", data);
|
||||
EXPECT_TRUE(res) << "read shall return true on existent files and valid Id";
|
||||
EXPECT_NE(data.size(), 0U) << "Vector shall contain data";
|
||||
|
||||
for(auto d : data) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_lookup, invalidId)
|
||||
{
|
||||
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||
CAMBEFileReader reader(indexFile, ambeFile);
|
||||
bool res = reader.read();
|
||||
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||
|
||||
std::vector<CAMBEData *> data;
|
||||
res = reader.lookup("This Id does not exist", data);
|
||||
EXPECT_FALSE(res) << "read shall return false on existent files and invalid Id";
|
||||
EXPECT_EQ(data.size(), 0U) << "Vector shall not contain data";
|
||||
|
||||
for(auto d : data) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <gtest/gtest.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "AMBEFileReader.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace AMBEFileReaderTests
|
||||
{
|
||||
class AMBEFileReader_read : public ::testing::Test {
|
||||
|
||||
};
|
||||
|
||||
TEST_F(AMBEFileReader_read, nonExistentFiles)
|
||||
{
|
||||
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_read, onlyIndexFileExists)
|
||||
{
|
||||
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_read, onlyAmbeFileExists)
|
||||
{
|
||||
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
|
||||
bool res = reader.read();
|
||||
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||
}
|
||||
|
||||
TEST_F(AMBEFileReader_read, bothFileExist)
|
||||
{
|
||||
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||
|
||||
CAMBEFileReader reader(indexFile, ambeFile);
|
||||
bool res = reader.read();
|
||||
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
|
||||
#include "APRSUtils.h"
|
||||
|
||||
namespace APRStoDPRSTests
|
||||
{
|
||||
class APRSUtils_calcGPSAIcomCRC : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(APRSUtils_calcGPSAIcomCRC, withCRCHeader)
|
||||
{
|
||||
auto crc = CAPRSUtils::calcGPSAIcomCRC("$$CRC6F5E,ABCDEF");
|
||||
|
||||
EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid";
|
||||
}
|
||||
|
||||
TEST_F(APRSUtils_calcGPSAIcomCRC, withoutCRCHeader)
|
||||
{
|
||||
auto crc = CAPRSUtils::calcGPSAIcomCRC("ABCDEF");
|
||||
|
||||
EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 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 <gtest/gtest.h>
|
||||
|
||||
#include "Utils.h"
|
||||
#include "StringUtils.h"
|
||||
#include "Log.h"
|
||||
|
||||
class Utils_swap_endian_le : public ::testing::Test {
|
||||
|
||||
};
|
||||
|
||||
// This test will fail on big endian systems....
|
||||
TEST_F(Utils_swap_endian_le, SwapUINT32_be) {
|
||||
uint32_t test = 0x12345678U;
|
||||
|
||||
uint32_t res = CUtils::swap_endian_le(test);
|
||||
|
||||
EXPECT_EQ(res, 0x78563412U);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=D-STAR Time Server Daemon
|
||||
After=network.target,network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=dstar
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in new issue