Add USRP2P25

This commit is contained in:
Doug McLain 2021-04-07 16:03:14 -04:00
parent 96184ff024
commit 71e8c23224
27 changed files with 2773 additions and 7 deletions

235
USRP2P25/Conf.cpp Normal file
View File

@ -0,0 +1,235 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 "Conf.h"
#include "Log.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
const int BUFFER_SIZE = 500;
enum SECTION {
SECTION_NONE,
SECTION_P25_NETWORK,
SECTION_USRP_NETWORK,
SECTION_LOG
};
CConf::CConf(const std::string& file) :
m_file(file),
m_callsign(),
m_daemon(false),
m_usrpAddress(),
m_usrpDstPort(0U),
m_usrpLocalPort(0U),
m_usrpGainAdjDb(),
m_usrpDebug(false),
m_p25DstId(0U),
m_p25DstAddress(),
m_p25DstPort(0U),
m_p25LocalAddress(),
m_p25LocalPort(0U),
m_p25NetworkDebug(false),
m_logDisplayLevel(0U),
m_logFileLevel(0U),
m_logFilePath(),
m_logFileRoot()
{
}
CConf::~CConf()
{
}
bool CConf::read()
{
FILE* fp = ::fopen(m_file.c_str(), "rt");
if (fp == NULL) {
::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str());
return false;
}
SECTION section = SECTION_NONE;
char buffer[BUFFER_SIZE];
while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) {
if (buffer[0U] == '#')
continue;
if (buffer[0U] == '[') {
if (::strncmp(buffer, "[P25 Network]", 13U) == 0)
section = SECTION_P25_NETWORK;
else if (::strncmp(buffer, "[USRP Network]", 14U) == 0)
section = SECTION_USRP_NETWORK;
else if (::strncmp(buffer, "[Log]", 5U) == 0)
section = SECTION_LOG;
else
section = SECTION_NONE;
continue;
}
char* key = ::strtok(buffer, " \t=\r\n");
if (key == NULL)
continue;
char* value = ::strtok(NULL, "\r\n");
if (value == NULL)
continue;
// Remove quotes from the value
size_t len = ::strlen(value);
if (len > 1U && *value == '"' && value[len - 1U] == '"') {
value[len - 1U] = '\0';
value++;
}
::fprintf(stderr, "CConf key:val:section == %s:%s:%d\n", key, value, section);
if (section == SECTION_P25_NETWORK) {
if (::strcmp(key, "Callsign") == 0)
m_callsign = value;
else if (::strcmp(key, "StartupDstId") == 0)
m_p25DstId = (unsigned int)::atoi(value);
else if (::strcmp(key, "LocalAddress") == 0)
m_p25LocalAddress = value;
else if (::strcmp(key, "LocalPort") == 0)
m_p25LocalPort = (unsigned int)::atoi(value);
else if (::strcmp(key, "DstAddress") == 0)
m_p25DstAddress = value;
else if (::strcmp(key, "DstPort") == 0)
m_p25DstPort = (unsigned int)::atoi(value);
else if (::strcmp(key, "GainAdjustdB") == 0)
m_p25GainAdjDb = value;
else if (::strcmp(key, "Debug") == 0)
m_p25NetworkDebug = ::atoi(value) == 1;
} else if (section == SECTION_USRP_NETWORK) {
if (::strcmp(key, "Address") == 0)
m_usrpAddress = value;
else if (::strcmp(key, "DstPort") == 0)
m_usrpDstPort = (uint32_t)::atoi(value);
else if (::strcmp(key, "LocalPort") == 0)
m_usrpLocalPort = (uint32_t)::atoi(value);
else if (::strcmp(key, "GainAdjustdB") == 0)
m_usrpGainAdjDb = value;
else if (::strcmp(key, "Debug") == 0)
m_usrpDebug = ::atoi(value) == 1;
} else if (section == SECTION_LOG) {
if (::strcmp(key, "FilePath") == 0)
m_logFilePath = value;
else if (::strcmp(key, "FileRoot") == 0)
m_logFileRoot = value;
else if (::strcmp(key, "FileLevel") == 0)
m_logFileLevel = (uint32_t)::atoi(value);
else if (::strcmp(key, "DisplayLevel") == 0)
m_logDisplayLevel = (uint32_t)::atoi(value);
}
}
::fclose(fp);
return true;
}
std::string CConf::getCallsign() const
{
return m_callsign;
}
std::string CConf::getP25DstAddress() const
{
return m_p25DstAddress;
}
unsigned int CConf::getP25DstPort() const
{
return m_p25DstPort;
}
std::string CConf::getP25LocalAddress() const
{
return m_p25LocalAddress;
}
unsigned int CConf::getP25LocalPort() const
{
return m_p25LocalPort;
}
std::string CConf::getP25GainAdjDb() const
{
return m_p25GainAdjDb;
}
bool CConf::getP25NetworkDebug() const
{
return m_p25NetworkDebug;
}
bool CConf::getDaemon() const
{
return m_daemon;
}
std::string CConf::getUSRPAddress() const
{
return m_usrpAddress;
}
uint16_t CConf::getUSRPDstPort() const
{
return m_usrpDstPort;
}
uint16_t CConf::getUSRPLocalPort() const
{
return m_usrpLocalPort;
}
std::string CConf::getUSRPGainAdjDb() const
{
return m_usrpGainAdjDb;
}
bool CConf::getUSRPDebug() const
{
return m_usrpDebug;
}
uint32_t CConf::getLogDisplayLevel() const
{
return m_logDisplayLevel;
}
uint32_t CConf::getLogFileLevel() const
{
return m_logFileLevel;
}
std::string CConf::getLogFilePath() const
{
return m_logFilePath;
}
std::string CConf::getLogFileRoot() const
{
return m_logFileRoot;
}

85
USRP2P25/Conf.h Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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.
*/
#if !defined(CONF_H)
#define CONF_H
#include <string>
#include <vector>
class CConf
{
public:
CConf(const std::string& file);
~CConf();
bool read();
// The P25 Network section
std::string getCallsign() const;
bool getDaemon() const;
uint32_t getP25DstId() const;
std::string getP25DstAddress() const;
uint32_t getP25DstPort() const;
std::string getP25LocalAddress() const;
uint32_t getP25LocalPort() const;
std::string getP25GainAdjDb() const;
bool getP25NetworkDebug() const;
// The USRP Network section
std::string getUSRPAddress() const;
uint16_t getUSRPDstPort() const;
uint16_t getUSRPLocalPort() const;
std::string getUSRPGainAdjDb() const;
bool getUSRPDebug() const;
// The Log section
uint32_t getLogDisplayLevel() const;
uint32_t getLogFileLevel() const;
std::string getLogFilePath() const;
std::string getLogFileRoot() const;
private:
std::string m_file;
std::string m_callsign;
bool m_daemon;
std::string m_usrpAddress;
uint16_t m_usrpDstPort;
uint16_t m_usrpLocalPort;
std::string m_usrpGainAdjDb;
bool m_usrpDebug;
uint32_t m_p25DstId;
std::string m_p25DstAddress;
uint32_t m_p25DstPort;
std::string m_p25LocalAddress;
uint32_t m_p25LocalPort;
std::string m_p25GainAdjDb;
bool m_p25NetworkDebug;
uint32_t m_logDisplayLevel;
uint32_t m_logFileLevel;
std::string m_logFilePath;
std::string m_logFileRoot;
};
#endif

136
USRP2P25/Log.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2015,2016 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 "Log.h"
#if defined(_WIN32) || defined(_WIN64)
#include <Windows.h>
#else
#include <sys/time.h>
#endif
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <ctime>
#include <cassert>
#include <cstring>
static unsigned int m_fileLevel = 2U;
static std::string m_filePath;
static std::string m_fileRoot;
static FILE* m_fpLog = NULL;
static unsigned int m_displayLevel = 2U;
static struct tm m_tm;
static char LEVELS[] = " DMIWEF";
static bool LogOpen()
{
if (m_fileLevel == 0U)
return true;
time_t now;
::time(&now);
struct tm* tm = ::gmtime(&now);
if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) {
if (m_fpLog != NULL)
return true;
} else {
if (m_fpLog != NULL)
::fclose(m_fpLog);
}
char filename[100U];
#if defined(_WIN32) || defined(_WIN64)
::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#else
::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#endif
m_fpLog = ::fopen(filename, "a+t");
m_tm = *tm;
return m_fpLog != NULL;
}
bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel)
{
m_filePath = filePath;
m_fileRoot = fileRoot;
m_fileLevel = fileLevel;
m_displayLevel = displayLevel;
return ::LogOpen();
}
void LogFinalise()
{
if (m_fpLog != NULL)
::fclose(m_fpLog);
}
void Log(unsigned int level, const char* fmt, ...)
{
assert(fmt != NULL);
char buffer[300U];
#if defined(_WIN32) || defined(_WIN64)
SYSTEMTIME st;
::GetSystemTime(&st);
::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
#else
struct timeval now;
::gettimeofday(&now, NULL);
struct tm* tm = ::gmtime(&now.tv_sec);
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U);
#endif
va_list vl;
va_start(vl, fmt);
::vsprintf(buffer + ::strlen(buffer), fmt, vl);
va_end(vl);
if (level >= m_fileLevel && m_fileLevel != 0U) {
bool ret = ::LogOpen();
if (!ret)
return;
::fprintf(m_fpLog, "%s\n", buffer);
::fflush(m_fpLog);
}
if (level >= m_displayLevel && m_displayLevel != 0U) {
::fprintf(stdout, "%s\n", buffer);
::fflush(stdout);
}
if (level == 6U) { // Fatal
::fclose(m_fpLog);
exit(1);
}
}

36
USRP2P25/Log.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2015,2016 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.
*/
#if !defined(LOG_H)
#define LOG_H
#include <string>
#define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__)
#define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__)
#define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__)
#define LogWarning(fmt, ...) Log(4U, fmt, ##__VA_ARGS__)
#define LogError(fmt, ...) Log(5U, fmt, ##__VA_ARGS__)
#define LogFatal(fmt, ...) Log(6U, fmt, ##__VA_ARGS__)
extern void Log(unsigned int level, const char* fmt, ...);
extern bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel);
extern void LogFinalise();
#endif

113
USRP2P25/MBEVocoder.cpp Normal file
View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2020 by Doug McLain AD8DP
*
* 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 <cstring>
#include "MBEVocoder.h"
const uint8_t BIT_MASK_TABLE8[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U };
#define WRITE_BIT8(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE8[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE8[(i)&7])
#define READ_BIT8(p,i) (p[(i)>>3] & BIT_MASK_TABLE8[(i)&7])
MBEVocoder::MBEVocoder(void)
{
}
void MBEVocoder::decode_4400(int16_t *pcm, uint8_t *imbe)
{
int16_t frame[8U] = {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
unsigned int offset = 0U;
int16_t mask = 0x0800;
for (unsigned int i = 0U; i < 12U; i++, mask >>= 1, offset++)
frame[0U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0800;
for (unsigned int i = 0U; i < 12U; i++, mask >>= 1, offset++)
frame[1U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0800;
for (unsigned int i = 0U; i < 12U; i++, mask >>= 1, offset++)
frame[2U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0800;
for (unsigned int i = 0U; i < 12U; i++, mask >>= 1, offset++)
frame[3U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0400;
for (unsigned int i = 0U; i < 11U; i++, mask >>= 1, offset++)
frame[4U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0400;
for (unsigned int i = 0U; i < 11U; i++, mask >>= 1, offset++)
frame[5U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0400;
for (unsigned int i = 0U; i < 11U; i++, mask >>= 1, offset++)
frame[6U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
mask = 0x0040;
for (unsigned int i = 0U; i < 7U; i++, mask >>= 1, offset++)
frame[7U] |= READ_BIT8(imbe, offset) != 0x00U ? mask : 0x0000;
vocoder.imbe_decode(frame, pcm);
}
void MBEVocoder::encode_4400(int16_t *pcm, uint8_t *imbe)
{
int16_t frame_vector[8];
memset(imbe, 0, 9);
vocoder.imbe_encode(frame_vector, pcm);
//vocoder.set_gain_adjust(1.0);
uint32_t offset = 0U;
int16_t mask = 0x0800;
for (uint32_t i = 0U; i < 12U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[0U] & mask) != 0);
mask = 0x0800;
for (uint32_t i = 0U; i < 12U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[1U] & mask) != 0);
mask = 0x0800;
for (uint32_t i = 0U; i < 12U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[2U] & mask) != 0);
mask = 0x0800;
for (uint32_t i = 0U; i < 12U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[3U] & mask) != 0);
mask = 0x0400;
for (uint32_t i = 0U; i < 11U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[4U] & mask) != 0);
mask = 0x0400;
for (uint32_t i = 0U; i < 11U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[5U] & mask) != 0);
mask = 0x0400;
for (uint32_t i = 0U; i < 11U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[6U] & mask) != 0);
mask = 0x0040;
for (uint32_t i = 0U; i < 7U; i++, mask >>= 1, offset++)
WRITE_BIT8(imbe, offset, (frame_vector[7U] & mask) != 0);
}

37
USRP2P25/MBEVocoder.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2020 by Doug McLain AD8DP
*
* 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 INCLUDED_AMBE_ENCODER_H
#define INCLUDED_AMBE_ENCODER_H
#include <stdint.h>
#include <imbe_vocoder_api.h>
class MBEVocoder {
public:
void decode_4400(int16_t *, uint8_t *);
void encode_4400(int16_t *, uint8_t *);
MBEVocoder(void);
private:
imbe_vocoder vocoder;
};
#endif /* INCLUDED_AMBE_ENCODER_H */

22
USRP2P25/Makefile Normal file
View File

@ -0,0 +1,22 @@
CC ?= gcc
CXX ?= g++
CFLAGS ?= -g -O3 -Wall -std=c++0x -pthread
LIBS = -lm -lpthread -limbe_vocoder
LDFLAGS ?= -g
OBJECTS = Conf.o Log.o MBEVocoder.o ModeConv.o P25Network.o StopWatch.o Timer.o UDPSocket.o USRPNetwork.o Utils.o USRP2P25.o
all: USRP2P25
USRP2P25: $(OBJECTS)
$(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o USRP2P25
%.o: %.cpp
$(CXX) $(CFLAGS) -c -o $@ $<
install:
install -m 755 USRP2P25 /usr/local/bin/
clean:
$(RM) USRP2P25 *.o *.d *.bak *~

216
USRP2P25/ModeConv.cpp Normal file
View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2010,2014,2016,2018 by Jonathan Naylor G4KLX
* Copyright (C) 2016 Mathias Weyland, HB9FRV
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 "ModeConv.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cstring>
#include <cmath>
const uint8_t IMBE_SILENCE[] = {0x04U, 0x0CU, 0xFDU, 0x7BU, 0xFBU, 0x7DU, 0xF2U, 0x7BU, 0x3DU, 0x9EU, 0x44};
CModeConv::CModeConv() :
m_p25N(0U),
m_usrpN(0U),
m_P25(5000U, "USRP2P25"),
m_USRP(5000U, "P252USRP"),
m_p25GainMultiplier(1),
m_p25Attenuate(false),
m_usrpGainMultiplier(3),
m_usrpAttenuate(true)
{
m_mbe = new MBEVocoder();
}
CModeConv::~CModeConv()
{
}
void CModeConv::setUSRPGainAdjDb(std::string dbstring)
{
float db = std::stof(dbstring);
float ratio = powf(10.0, (db/10.0));
if(db < 0){
ratio = 1/ratio;
m_usrpAttenuate = true;
}
m_usrpGainMultiplier = (uint16_t)roundf(ratio);
}
void CModeConv::setP25GainAdjDb(std::string dbstring)
{
float db = std::stof(dbstring);
float ratio = powf(10.0, (db/10.0));
if(db < 0){
ratio = 1/ratio;
m_p25Attenuate = true;
}
m_p25GainMultiplier = (uint16_t)roundf(ratio);
}
void CModeConv::putUSRPHeader()
{
m_P25.addData(&TAG_HEADER, 1U);
m_P25.addData(IMBE_SILENCE, 11U);
m_p25N += 1U;
}
void CModeConv::putUSRPEOT()
{
m_P25.addData(&TAG_EOT, 1U);
m_P25.addData(IMBE_SILENCE, 11U);
m_p25N += 1U;
}
void CModeConv::putUSRP(int16_t* data)
{
uint8_t imbe[11U];
int16_t audio_adjusted[160U];
for(int i = 0; i < 160; ++i){
audio_adjusted[i] = m_usrpAttenuate ? data[i] / m_usrpGainMultiplier : data[i] * m_usrpGainMultiplier;
}
m_mbe->encode_4400(audio_adjusted, imbe);
m_P25.addData(&TAG_DATA, 1U);
m_P25.addData(imbe, 11U);
//CUtils::dump(1U, "NXDN Voice:", data, 9U);
m_p25N += 1U;
}
void CModeConv::putP25(uint8_t* data)
{
int16_t audio[160U];
int16_t audio_adjusted[160U];
uint8_t imbe[11U];
switch (data[0U]) {
case 0x62U:
::memcpy(imbe, data + 10U, 11U);
break;
case 0x63U:
::memcpy(imbe, data + 1U, 11U);
break;
case 0x64U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x65U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x66U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x67U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x68U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x69U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x6AU:
::memcpy(imbe, data + 4U, 11U);
break;
case 0x6BU:
::memcpy(imbe, data + 10U, 11U);
break;
case 0x6CU:
::memcpy(imbe, data + 1U, 11U);
break;
case 0x6DU:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x6EU:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x6FU:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x70U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x71U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x72U:
::memcpy(imbe, data + 5U, 11U);
break;
case 0x73U:
::memcpy(imbe, data + 4U, 11U);
break;
default:
break;
}
m_mbe->decode_4400(audio, imbe);
for(int i = 0; i < 160; ++i){
audio_adjusted[i] = m_p25Attenuate ? audio[i] / m_p25GainMultiplier : audio[i] * m_p25GainMultiplier;
}
m_USRP.addData(audio_adjusted, 160U);
m_usrpN += 1U;
}
bool CModeConv::getUSRP(int16_t* data)
{
if(m_usrpN){
--m_usrpN;
return m_USRP.getData(data, 160U);
}
else{
return false;
}
}
uint32_t CModeConv::getP25(uint8_t* data)
{
uint8_t tag[1U];
tag[0U] = TAG_NODATA;
if (m_p25N >= 1U) {
m_P25.peek(tag, 1U);
if (tag[0U] != TAG_DATA) {
m_P25.getData(tag, 1U);
m_P25.getData(data, 11U);
m_p25N -= 1U;
return tag[0U];
}
}
if (m_p25N >= 1U) {
m_P25.getData(tag, 1U);
m_P25.getData(data, 11U);
m_p25N -= 1U;
return TAG_DATA;
}
else
return TAG_NODATA;
}

60
USRP2P25/ModeConv.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2010,2014,2016,2018 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 "RingBuffer.h"
#include "MBEVocoder.h"
const unsigned char TAG_HEADER = 0x00U;
const unsigned char TAG_DATA = 0x01U;
const unsigned char TAG_LOST = 0x02U;
const unsigned char TAG_EOT = 0x03U;
const unsigned char TAG_NODATA = 0x04U;
#if !defined(MODECONV_H)
#define MODECONV_H
class CModeConv {
public:
CModeConv();
~CModeConv();
void setUSRPGainAdjDb(std::string dbstring);
void setP25GainAdjDb(std::string dbstring);
void putUSRP(int16_t* data);
void putUSRPHeader();
void putUSRPEOT();
void putP25(unsigned char* data);
uint32_t getP25(uint8_t* data);
bool getUSRP(int16_t* data);
private:
uint32_t m_p25N;
uint32_t m_usrpN;
CRingBuffer<uint8_t> m_P25;
CRingBuffer<int16_t> m_USRP;
MBEVocoder *m_mbe;
uint16_t m_p25GainMultiplier;
bool m_p25Attenuate;
uint16_t m_usrpGainMultiplier;
bool m_usrpAttenuate;
};
#endif

118
USRP2P25/P25Network.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2009-2014,2016 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 "P25Network.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cassert>
#include <cstring>
CP25Network::CP25Network(const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, const std::string& callsign, bool debug) :
m_callsign(callsign),
m_address(),
m_port(gatewayPort),
m_socket(localAddress, localPort),
m_debug(debug)
{
m_callsign.resize(10U, ' ');
m_address = CUDPSocket::lookup(gatewayAddress);
}
CP25Network::~CP25Network()
{
}
bool CP25Network::open()
{
LogInfo("Opening P25 network connection");
return m_socket.open();
}
bool CP25Network::writeData(const unsigned char* data, unsigned int length)
{
assert(data != NULL);
assert(length > 0U);
if (m_debug)
CUtils::dump(1U, "P25 Network Data Sent", data, length);
return m_socket.write(data, length, m_address, m_port);
}
bool CP25Network::writePoll()
{
unsigned char data[15U];
data[0U] = 0xF0U;
for (unsigned int i = 0U; i < 10U; i++)
data[i + 1U] = m_callsign.at(i);
if (m_debug)
CUtils::dump(1U, "P25 Network Poll Sent", data, 11U);
return m_socket.write(data, 11U, m_address, m_port);
}
bool CP25Network::writeUnlink()
{
unsigned char data[15U];
data[0U] = 0xF1U;
for (unsigned int i = 0U; i < 10U; i++)
data[i + 1U] = m_callsign.at(i);
if (m_debug)
CUtils::dump(1U, "P25 Network Unlink Sent", data, 11U);
return m_socket.write(data, 11U, m_address, m_port);
}
unsigned int CP25Network::readData(unsigned char* data, unsigned int length)
{
assert(data != NULL);
assert(length > 0U);
in_addr address;
unsigned int port;
int len = m_socket.read(data, length, address, port);
if (len <= 0)
return 0U;
// Check if the data is for us
if (m_address.s_addr != address.s_addr || port != m_port) {
LogMessage("P25 packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_port, port);
return 0U;
}
if (m_debug)
CUtils::dump(1U, "P25 Network Data Received", data, len);
return len;
}
void CP25Network::close()
{
m_socket.close();
LogInfo("Closing P25 network connection");
}

52
USRP2P25/P25Network.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2009-2014,2016 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 P25Network_H
#define P25Network_H
#include "UDPSocket.h"
#include <cstdint>
#include <string>
class CP25Network {
public:
CP25Network(const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, const std::string& callsign, bool debug);
~CP25Network();
bool open();
bool writeData(const unsigned char* data, unsigned int length);
unsigned int readData(unsigned char* data, unsigned int length);
bool writePoll();
bool writeUnlink();
void close();
private:
std::string m_callsign;
in_addr m_address;
unsigned int m_port;
CUDPSocket m_socket;
bool m_debug;
};
#endif

11
USRP2P25/README.md Normal file
View File

@ -0,0 +1,11 @@
# Description
This is the source code of USRP2P25, which converts USRP PCM audio and P25 digital mode, based on Jonathan G4KLX's [MMDVM](https://github.com/g4klx) software. Typical uses are connecting P25 reflectors to AllStar nodes and can be used with MMDVM modems in FM mode as stand alone radios.
# Configuration
P25 and USRP both have a configuration value 'GainAdjustDB". Thru trial and error I have found the best balance of audio levels which are set as the defaults in the provided USRP2P25.ini file. For AllStar connections, USRP address and ports are the values defined in your USRP channel based node.
# Building
Build and install imbe_vocoder: https://github.com/nostar/imbe_vocoder
run 'make' from the source directory.

154
USRP2P25/RingBuffer.h Normal file
View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2006-2009,2012,2013,2015,2016 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 RingBuffer_H
#define RingBuffer_H
#include "Log.h"
#include <cstdio>
#include <cassert>
#include <cstring>
template<class T> class CRingBuffer {
public:
CRingBuffer(unsigned int length, const char* name) :
m_length(length),
m_name(name),
m_buffer(NULL),
m_iPtr(0U),
m_oPtr(0U)
{
assert(length > 0U);
assert(name != NULL);
m_buffer = new T[length];
::memset(m_buffer, 0x00, m_length * sizeof(T));
}
~CRingBuffer()
{
delete[] m_buffer;
}
bool addData(const T* buffer, unsigned int nSamples)
{
if (nSamples >= freeSpace()) {
LogError("%s buffer overflow, clearing the buffer. (%u >= %u)", m_name, nSamples, freeSpace());
clear();
return false;
}
for (unsigned int i = 0U; i < nSamples; i++) {
m_buffer[m_iPtr++] = buffer[i];
if (m_iPtr == m_length)
m_iPtr = 0U;
}
return true;
}
bool getData(T* buffer, unsigned int nSamples)
{
if (dataSize() < nSamples) {
LogError("**** Underflow in %s ring buffer, %u < %u", m_name, dataSize(), nSamples);
return false;
}
for (unsigned int i = 0U; i < nSamples; i++) {
buffer[i] = m_buffer[m_oPtr++];
if (m_oPtr == m_length)
m_oPtr = 0U;
}
return true;
}
bool peek(T* buffer, unsigned int nSamples)
{
if (dataSize() < nSamples) {
LogError("**** Underflow peek in %s ring buffer, %u < %u", m_name, dataSize(), nSamples);
return false;
}
unsigned int ptr = m_oPtr;
for (unsigned int i = 0U; i < nSamples; i++) {
buffer[i] = m_buffer[ptr++];
if (ptr == m_length)
ptr = 0U;
}
return true;
}
void clear()
{
m_iPtr = 0U;
m_oPtr = 0U;
::memset(m_buffer, 0x00, m_length * sizeof(T));
}
unsigned int freeSpace() const
{
unsigned int len = m_length;
if (m_oPtr > m_iPtr)
len = m_oPtr - m_iPtr;
else if (m_iPtr > m_oPtr)
len = m_length - (m_iPtr - m_oPtr);
if (len > m_length)
len = 0U;
return len;
}
unsigned int dataSize() const
{
return m_length - freeSpace();
}
bool hasSpace(unsigned int length) const
{
return freeSpace() > length;
}
bool hasData() const
{
return m_oPtr != m_iPtr;
}
bool isEmpty() const
{
return m_oPtr == m_iPtr;
}
private:
unsigned int m_length;
const char* m_name;
T* m_buffer;
unsigned int m_iPtr;
unsigned int m_oPtr;
};
#endif

105
USRP2P25/StopWatch.cpp Normal file
View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "StopWatch.h"
#if defined(_WIN32) || defined(_WIN64)
CStopWatch::CStopWatch() :
m_frequencyS(),
m_frequencyMS(),
m_start()
{
::QueryPerformanceFrequency(&m_frequencyS);
m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL;
}
CStopWatch::~CStopWatch()
{
}
unsigned long long CStopWatch::time() const
{
LARGE_INTEGER now;
::QueryPerformanceCounter(&now);
return (unsigned long long)(now.QuadPart / m_frequencyMS.QuadPart);
}
unsigned long long CStopWatch::start()
{
::QueryPerformanceCounter(&m_start);
return (unsigned long long)(m_start.QuadPart / m_frequencyS.QuadPart);
}
unsigned int CStopWatch::elapsed()
{
LARGE_INTEGER now;
::QueryPerformanceCounter(&now);
LARGE_INTEGER temp;
temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000;
return (unsigned int)(temp.QuadPart / m_frequencyS.QuadPart);
}
#else
#include <cstdio>
#include <ctime>
CStopWatch::CStopWatch() :
m_startMS(0ULL)
{
}
CStopWatch::~CStopWatch()
{
}
unsigned long long CStopWatch::time() const
{
struct timeval now;
::gettimeofday(&now, NULL);
return now.tv_sec * 1000ULL + now.tv_usec / 1000ULL;
}
unsigned long long CStopWatch::start()
{
struct timespec now;
::clock_gettime(CLOCK_MONOTONIC, &now);
m_startMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL;
return m_startMS;
}
unsigned int CStopWatch::elapsed()
{
struct timespec now;
::clock_gettime(CLOCK_MONOTONIC, &now);
unsigned long long nowMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL;
return nowMS - m_startMS;
}
#endif

49
USRP2P25/StopWatch.h Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(STOPWATCH_H)
#define STOPWATCH_H
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#else
#include <sys/time.h>
#endif
class CStopWatch
{
public:
CStopWatch();
~CStopWatch();
unsigned long long time() const;
unsigned long long start();
unsigned int elapsed();
private:
#if defined(_WIN32) || defined(_WIN64)
LARGE_INTEGER m_frequencyS;
LARGE_INTEGER m_frequencyMS;
LARGE_INTEGER m_start;
#else
unsigned long long m_startMS;
#endif
};
#endif

68
USRP2P25/Timer.cpp Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2009,2010,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 "Timer.h"
#include <cstdio>
#include <cassert>
CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) :
m_ticksPerSec(ticksPerSec),
m_timeout(0U),
m_timer(0U)
{
assert(ticksPerSec > 0U);
if (secs > 0U || msecs > 0U) {
// m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U;
unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec;
m_timeout = (unsigned int)(temp / 1000ULL + 1ULL);
}
}
CTimer::~CTimer()
{
}
void CTimer::setTimeout(unsigned int secs, unsigned int msecs)
{
if (secs > 0U || msecs > 0U) {
// m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U;
unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec;
m_timeout = (unsigned int)(temp / 1000ULL + 1ULL);
} else {
m_timeout = 0U;
m_timer = 0U;
}
}
unsigned int CTimer::getTimeout() const
{
if (m_timeout == 0U)
return 0U;
return (m_timeout - 1U) / m_ticksPerSec;
}
unsigned int CTimer::getTimer() const
{
if (m_timer == 0U)
return 0U;
return (m_timer - 1U) / m_ticksPerSec;
}

89
USRP2P25/Timer.h Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2009,2010,2011,2014 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 Timer_H
#define Timer_H
class CTimer {
public:
CTimer(unsigned int ticksPerSec, unsigned int secs = 0U, unsigned int msecs = 0U);
~CTimer();
void setTimeout(unsigned int secs, unsigned int msecs = 0U);
unsigned int getTimeout() const;
unsigned int getTimer() const;
unsigned int getRemaining()
{
if (m_timeout == 0U || m_timer == 0U)
return 0U;
if (m_timer >= m_timeout)
return 0U;
return (m_timeout - m_timer) / m_ticksPerSec;
}
bool isRunning()
{
return m_timer > 0U;
}
void start(unsigned int secs, unsigned int msecs = 0U)
{
setTimeout(secs, msecs);
start();
}
void start()
{
if (m_timeout > 0U)
m_timer = 1U;
}
void stop()
{
m_timer = 0U;
}
bool hasExpired()
{
if (m_timeout == 0U || m_timer == 0U)
return false;
if (m_timer >= m_timeout)
return true;
return false;
}
void clock(unsigned int ticks = 1U)
{
if (m_timer > 0U && m_timeout > 0U)
m_timer += ticks;
}
private:
unsigned int m_ticksPerSec;
unsigned int m_timeout;
unsigned int m_timer;
};
#endif

212
USRP2P25/UDPSocket.cpp Normal file
View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2006-2016 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 "UDPSocket.h"
#include "Log.h"
#include <cassert>
#include <cerrno>
#include <cstring>
CUDPSocket::CUDPSocket(const std::string& address, unsigned int port) :
m_address(address),
m_port(port),
m_fd(-1)
{
assert(!address.empty());
}
CUDPSocket::CUDPSocket(unsigned int port) :
m_address(),
m_port(port),
m_fd(-1)
{
}
CUDPSocket::~CUDPSocket()
{
}
in_addr CUDPSocket::lookup(const std::string& hostname)
{
in_addr addr;
in_addr_t address = ::inet_addr(hostname.c_str());
if (address != in_addr_t(-1)) {
addr.s_addr = address;
LogInfo("inet_addr() returns %x", addr.s_addr);
return addr;
}
struct hostent* hp = ::gethostbyname(hostname.c_str());
if (hp != NULL) {
::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr));
LogInfo("gethostbyname() returns %x", addr.s_addr);
return addr;
}
LogError("Cannot find address for host %s", hostname.c_str());
addr.s_addr = INADDR_NONE;
return addr;
}
bool CUDPSocket::open()
{
m_fd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (m_fd < 0) {
LogError("Cannot create the UDP socket, err: %d", errno);
return false;
}
if (m_port > 0U) {
sockaddr_in addr;
::memset(&addr, 0x00, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(m_port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (!m_address.empty()) {
addr.sin_addr.s_addr = ::inet_addr(m_address.c_str());
if (addr.sin_addr.s_addr == INADDR_NONE) {
LogError("The local address is invalid - %s", m_address.c_str());
return false;
}
}
int reuse = 1;
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) {
LogError("Cannot set the UDP socket option, err: %d", errno);
return false;
}
if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) {
LogError("Cannot bind the UDP address, err: %d", errno);
return false;
}
}
return true;
}
int CUDPSocket::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port)
{
assert(buffer != NULL);
assert(length > 0U);
// Check that the readfrom() won't block
fd_set readFds;
FD_ZERO(&readFds);
#if defined(_WIN32) || defined(_WIN64)
FD_SET((unsigned int)m_fd, &readFds);
#else
FD_SET(m_fd, &readFds);
#endif
// Return immediately
timeval tv;
tv.tv_sec = 0L;
tv.tv_usec = 0L;
int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv);
if (ret < 0) {
#if defined(_WIN32) || defined(_WIN64)
LogError("Error returned from UDP select, err: %lu", ::GetLastError());
#else
LogError("Error returned from UDP select, err: %d", errno);
#endif
return -1;
}
if (ret == 0)
return 0;
sockaddr_in addr;
#if defined(_WIN32) || defined(_WIN64)
int size = sizeof(sockaddr_in);
#else
socklen_t size = sizeof(sockaddr_in);
#endif
#if defined(_WIN32) || defined(_WIN64)
int len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size);
#else
ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size);
#endif
if (len <= 0) {
#if defined(_WIN32) || defined(_WIN64)
LogError("Error returned from recvfrom, err: %lu", ::GetLastError());
#else
LogError("Error returned from recvfrom, err: %d", errno);
#endif
return -1;
}
address = addr.sin_addr;
port = ntohs(addr.sin_port);
return len;
}
bool CUDPSocket::write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port)
{
assert(buffer != NULL);
assert(length > 0U);
sockaddr_in addr;
::memset(&addr, 0x00, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr = address;
addr.sin_port = htons(port);
ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in));
if (ret < 0) {
LogError("Error returned from sendto, err: %d", errno);
return false;
}
if (ret != ssize_t(length))
return false;
return true;
}
void CUDPSocket::close()
{
#if defined(_WIN32) || defined(_WIN64)
::closesocket(m_fd);
#else
::close(m_fd);
#endif
}

54
USRP2P25/UDPSocket.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2009-2011,2013,2015,2016 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 UDPSocket_H
#define UDPSocket_H
#include <string>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
class CUDPSocket {
public:
CUDPSocket(const std::string& address, unsigned int port = 0U);
CUDPSocket(unsigned int port = 0U);
~CUDPSocket();
bool open();
int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port);
bool write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port);
void close();
static in_addr lookup(const std::string& hostName);
private:
std::string m_address;
unsigned short m_port;
int m_fd;
};
#endif

524
USRP2P25/USRP2P25.cpp Normal file
View File

@ -0,0 +1,524 @@
/*
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 "USRP2P25.h"
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
const unsigned char REC62[] = {
0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC63[] = {
0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC64[] = {
0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC65[] = {
0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC66[] = {
0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC67[] = {
0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC68[] = {
0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC69[] = {
0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6A[] = {
0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC6B[] = {
0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC6C[] = {
0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6D[] = {
0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6E[] = {
0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC6F[] = {
0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC70[] = {
0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC71[] = {
0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC72[] = {
0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U};
const unsigned char REC73[] = {
0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
const unsigned char REC80[] = {
0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
#define USRP_FRAME_PER 15U
#define P25_FRAME_PER 15U
const char* DEFAULT_INI_FILE = "/etc/USRP2P25.ini";
const char* HEADER1 = "This software is for use on amateur radio networks only,";
const char* HEADER2 = "it is to be used for educational purposes only. Its use on";
const char* HEADER3 = "commercial networks is strictly prohibited.";
const char* HEADER4 = "Copyright(C) 2021 by AD8DP";
#include <functional>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <clocale>
#include <cctype>
static bool m_killed = false;
void sig_handler(int signo)
{
if (signo == SIGTERM) {
m_killed = true;
::fprintf(stdout, "Received SIGTERM\n");
}
}
int main(int argc, char** argv)
{
const char* iniFile = DEFAULT_INI_FILE;
if (argc > 1) {
for (int currentArg = 1; currentArg < argc; ++currentArg) {
std::string arg = argv[currentArg];
if ((arg == "-v") || (arg == "--version")) {
::fprintf(stdout, "USRP2P25 version %s\n", VERSION);
return 0;
} else if (arg.substr(0, 1) == "-") {
::fprintf(stderr, "Usage: USRP2P25 [-v|--version] [filename]\n");
return 1;
} else {
iniFile = argv[currentArg];
}
}
}
// Capture SIGTERM to finish gracelessly
if (signal(SIGTERM, sig_handler) == SIG_ERR)
::fprintf(stdout, "Can't catch SIGTERM\n");
CUSRP2P25* gateway = new CUSRP2P25(std::string(iniFile));
int ret = gateway->run();
delete gateway;
return ret;
}
CUSRP2P25::CUSRP2P25(const std::string& configFile) :
m_callsign(),
m_conf(configFile),
m_usrpNetwork(NULL),
m_p25Network(NULL),
m_conv(),
m_p25Src(1U),
m_p25Dst(1U),
m_p25Frame(NULL),
m_p25Frames(0U),
m_p25info(false),
m_usrpFrame(NULL),
m_usrpFrames(0U)
{
m_p25Frame = new uint8_t[100U];
m_usrpFrame = new uint8_t[400U];
::memset(m_p25Frame, 0U, 100U);
::memset(m_usrpFrame, 0U, 400U);
}
CUSRP2P25::~CUSRP2P25()
{
delete[] m_p25Frame;
delete[] m_usrpFrame;
}
int CUSRP2P25::run()
{
bool ret = m_conf.read();
if (!ret) {
::fprintf(stderr, "USRP2P25: cannot read the .ini file\n");
return 1;
}
//setlocale(LC_ALL, "C");
uint32_t logDisplayLevel = m_conf.getLogDisplayLevel();
if(m_conf.getDaemon())
logDisplayLevel = 0U;
bool m_daemon = m_conf.getDaemon();
if (m_daemon) {
// Create new process
pid_t pid = ::fork();
if (pid == -1) {
::fprintf(stderr, "Couldn't fork() , exiting\n");
return -1;
} else if (pid != 0)
exit(EXIT_SUCCESS);
// Create new session and process group
if (::setsid() == -1) {
::fprintf(stderr, "Couldn't setsid(), exiting\n");
return -1;
}
// Set the working directory to the root directory
if (::chdir("/") == -1) {
::fprintf(stderr, "Couldn't cd /, exiting\n");
return -1;
}
// If we are currently root...
if (getuid() == 0) {
struct passwd* user = ::getpwnam("mmdvm");
if (user == NULL) {
::fprintf(stderr, "Could not get the mmdvm user, exiting\n");
return -1;
}
uid_t mmdvm_uid = user->pw_uid;
gid_t mmdvm_gid = user->pw_gid;
// Set user and group ID's to mmdvm:mmdvm
if (setgid(mmdvm_gid) != 0) {
::fprintf(stderr, "Could not set mmdvm GID, exiting\n");
return -1;
}
if (setuid(mmdvm_uid) != 0) {
::fprintf(stderr, "Could not set mmdvm UID, exiting\n");
return -1;
}
// Double check it worked (AKA Paranoia)
if (setuid(0) != -1) {
::fprintf(stderr, "It's possible to regain root - something is wrong!, exiting\n");
return -1;
}
}
}
ret = ::LogInitialise(m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), logDisplayLevel);
if (!ret) {
::fprintf(stderr, "USRP2P25: unable to open the log file\n");
return 1;
}
if (m_daemon) {
::close(STDIN_FILENO);
::close(STDOUT_FILENO);
::close(STDERR_FILENO);
}
LogInfo(HEADER1);
LogInfo(HEADER2);
LogInfo(HEADER3);
LogInfo(HEADER4);
m_callsign = m_conf.getCallsign();
std::string p25_dstAddress = m_conf.getP25DstAddress();
unsigned int p25_dstPort = m_conf.getP25DstPort();
std::string p25_localAddress = m_conf.getP25LocalAddress();
unsigned int p25_localPort = m_conf.getP25LocalPort();
bool p25_debug = m_conf.getP25NetworkDebug();
::fprintf(stderr, "%s : %s\n", p25_dstAddress.c_str(), p25_localAddress.c_str());
m_p25Network = new CP25Network(p25_localAddress, p25_localPort, p25_dstAddress, p25_dstPort, m_callsign, p25_debug);
ret = m_p25Network->open();
if (!ret) {
::LogError("Cannot open the P25 network port");
::LogFinalise();
return 1;
}
m_p25Network->writePoll();
std::string usrp_address = m_conf.getUSRPAddress();
uint16_t usrp_dstPort = m_conf.getUSRPDstPort();
uint16_t usrp_localPort = m_conf.getUSRPLocalPort();
bool usrp_debug = m_conf.getUSRPDebug();
m_conv.setUSRPGainAdjDb(m_conf.getUSRPGainAdjDb());
m_usrpNetwork = new CUSRPNetwork(usrp_address, usrp_dstPort, usrp_localPort, usrp_debug);
ret = m_usrpNetwork->open();
if (!ret) {
::LogError("Cannot open the USRP network port");
::LogFinalise();
return 1;
}
CTimer networkWatchdog(100U, 0U, 1500U);
CTimer pollTimer(1000U, 8U);
CStopWatch stopWatch;
CStopWatch p25Watch;
CStopWatch usrpWatch;
pollTimer.start();
stopWatch.start();
p25Watch.start();
usrpWatch.start();
uint16_t p25_cnt = 0;
uint32_t usrp_cnt = 0;
LogMessage("Starting USRP2P25-%s", VERSION);
for (; m_killed == 0;) {
uint8_t buffer[2000U];
memset(buffer, 0, sizeof(buffer));
uint32_t ms = stopWatch.elapsed();
if (p25Watch.elapsed() > P25_FRAME_PER) {
unsigned int p25FrameType = m_conv.getP25(m_p25Frame);
if(p25FrameType == TAG_HEADER) {
p25_cnt = 0U;
p25Watch.start();
}
else if(p25FrameType == TAG_EOT) {
m_p25Network->writeData(REC80, 17U);
p25Watch.start();
}
else if(p25FrameType == TAG_DATA) {
unsigned int p25step = p25_cnt % 18U;
//CUtils::dump(1U, "P25 Data", m_p25Frame, 11U);
if (p25_cnt > 2U) {
switch (p25step) {
case 0x00U:
::memcpy(buffer, REC62, 22U);
::memcpy(buffer + 10U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 22U);
break;
case 0x01U:
::memcpy(buffer, REC63, 14U);
::memcpy(buffer + 1U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 14U);
break;
case 0x02U:
::memcpy(buffer, REC64, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
buffer[1U] = 0x00U;
m_p25Network->writeData(buffer, 17U);
break;
case 0x03U:
::memcpy(buffer, REC65, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
buffer[1U] = (m_p25Dst >> 16) & 0xFFU;
buffer[2U] = (m_p25Dst >> 8) & 0xFFU;
buffer[3U] = (m_p25Dst >> 0) & 0xFFU;
m_p25Network->writeData(buffer, 17U);
break;
case 0x04U:
::memcpy(buffer, REC66, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
buffer[1U] = (m_p25Src >> 16) & 0xFFU;
buffer[2U] = (m_p25Src >> 8) & 0xFFU;
buffer[3U] = (m_p25Src >> 0) & 0xFFU;
m_p25Network->writeData(buffer, 17U);
break;
case 0x05U:
::memcpy(buffer, REC67, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x06U:
::memcpy(buffer, REC68, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x07U:
::memcpy(buffer, REC69, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x08U:
::memcpy(buffer, REC6A, 16U);
::memcpy(buffer + 4U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 16U);
break;
case 0x09U:
::memcpy(buffer, REC6B, 22U);
::memcpy(buffer + 10U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 22U);
break;
case 0x0AU:
::memcpy(buffer, REC6C, 14U);
::memcpy(buffer + 1U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 14U);
break;
case 0x0BU:
::memcpy(buffer, REC6D, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x0CU:
::memcpy(buffer, REC6E, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x0DU:
::memcpy(buffer, REC6F, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x0EU:
::memcpy(buffer, REC70, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
buffer[1U] = 0x80U;
m_p25Network->writeData(buffer, 17U);
break;
case 0x0FU:
::memcpy(buffer, REC71, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x10U:
::memcpy(buffer, REC72, 17U);
::memcpy(buffer + 5U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 17U);
break;
case 0x11U:
::memcpy(buffer, REC73, 16U);
::memcpy(buffer + 4U, m_p25Frame, 11U);
m_p25Network->writeData(buffer, 16U);
break;
}
}
p25_cnt++;
p25Watch.start();
}
}
while (m_p25Network->readData(m_p25Frame, 22U) > 0U) {
//CUtils::dump(1U, "P25 Data", m_p25Frame, 22U);
if (m_p25Frame[0U] != 0xF0U && m_p25Frame[0U] != 0xF1U) {
if (m_p25Frame[0U] == 0x62U && !m_p25info) {
m_p25Frames = 0;
//m_conv.putP25Header();
} else if (m_p25Frame[0U] == 0x65U && !m_p25info) {
m_p25Dst = (m_p25Frame[1U] << 16) & 0xFF0000U;
m_p25Dst |= (m_p25Frame[2U] << 8) & 0x00FF00U;
m_p25Dst |= (m_p25Frame[3U] << 0) & 0x0000FFU;
} else if (m_p25Frame[0U] == 0x66U && !m_p25info) {
m_p25Src = (m_p25Frame[1U] << 16) & 0xFF0000U;
m_p25Src |= (m_p25Frame[2U] << 8) & 0x00FF00U;
m_p25Src |= (m_p25Frame[3U] << 0) & 0x0000FFU;
LogMessage("Received P25 audio: Src: %d Dst: %d", m_p25Src, m_p25Dst);
m_p25info = true;
} else if (m_p25Frame[0U] == 0x80U) {
LogMessage("P25 received end of voice transmission, %.1f seconds", float(m_p25Frames) / 50.0F);
m_p25info = false;
//m_conv.putP25EOT();
}
m_conv.putP25(m_p25Frame);
m_p25Frames++;
}
}
if (usrpWatch.elapsed() > USRP_FRAME_PER) {
int16_t pcm[160];
if(m_conv.getUSRP(pcm)){
//CUtils::dump(1U, "USRP data:", m_usrpFrame, 33U);
const uint32_t cnt = htonl(usrp_cnt);
memcpy(m_usrpFrame, "USRP", 4);
memset(m_usrpFrame+4, 0, 28);
memcpy(m_usrpFrame+4, &cnt, 4);
m_usrpFrame[15] = 1;
for(int i = 0; i < 320; i+=2){
m_usrpFrame[32+i] = pcm[(i/2)] & 0xff;
m_usrpFrame[32+i+1] = pcm[(i/2)] >> 8;
}
m_usrpNetwork->writeData(m_usrpFrame, 352);
usrp_cnt++;
usrpWatch.start();
}
}
uint32_t len = 0;
while ( (len = m_usrpNetwork->readData(m_usrpFrame, 400)) ) {
if((len == 32) && !memcmp(m_usrpFrame, "USRP", 4)) {
LogMessage("USRP received end of voice transmission, %.1f seconds", float(m_usrpFrames) / 50.0F);
m_conv.putUSRPEOT();
m_usrpFrames = 0U;
}
if( (!memcmp(m_usrpFrame, "USRP", 4)) && len == 352) {
if(!m_usrpFrames){
m_conv.putUSRPHeader();
LogMessage("USRP first frame received");
}
int16_t pcm[160];
for(int i = 0; i < 160; ++i){
pcm[i] = (m_usrpFrame[32+(i*2)+1] << 8) | m_usrpFrame[32+(i*2)];
}
m_conv.putUSRP(pcm);
m_usrpFrames++;
}
}
stopWatch.start();
pollTimer.clock(ms);
if (pollTimer.isRunning() && pollTimer.hasExpired()) {
m_p25Network->writePoll();
pollTimer.start();
}
if (ms < 5U) ::usleep(5U * 1000U);
}
m_p25Network->close();
m_usrpNetwork->close();
delete m_usrpNetwork;
delete m_p25Network;
::LogFinalise();
return 0;
}

58
USRP2P25/USRP2P25.h Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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.
*/
#if !defined(USRP2P25_H)
#define USRP2P25_H
#include "ModeConv.h"
#include "USRPNetwork.h"
#include "P25Network.h"
#include "UDPSocket.h"
#include "StopWatch.h"
#include "Version.h"
#include "Timer.h"
#include "Utils.h"
#include "Conf.h"
#include "Log.h"
#include <string>
class CUSRP2P25
{
public:
CUSRP2P25(const std::string& configFile);
~CUSRP2P25();
int run();
private:
std::string m_callsign;
CConf m_conf;
CUSRPNetwork* m_usrpNetwork;
CP25Network* m_p25Network;
CModeConv m_conv;
uint32_t m_p25Src;
uint32_t m_p25Dst;
uint8_t* m_p25Frame;
uint32_t m_p25Frames;
bool m_p25info;
uint8_t* m_usrpFrame;
uint32_t m_usrpFrames;
};
#endif

View File

@ -1,11 +1,11 @@
[M17 Network]
Callsign=AD8DP D
Address=51.81.119.111
Name=M17-M17 C
[P25 Network]
Callsign=AD8DP
LocalAddress=127.0.0.1
LocalPort=32010
DstPort=17000
GainAdjustdB=3
DstAddress=127.0.0.1
DstPort=42020
Daemon=0
GainAdjustdB=3
Debug=1
[USRP Network]
@ -20,5 +20,5 @@ Debug=1
DisplayLevel=1
FileLevel=1
FilePath=.
FileRoot=USRP2M17
FileRoot=USRP2P25

78
USRP2P25/USRPNetwork.cpp Normal file
View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 "USRPNetwork.h"
#include "StopWatch.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
CUSRPNetwork::CUSRPNetwork(const std::string& address, uint16_t dstPort, uint16_t localPort, bool debug) :
m_address(),
m_port(dstPort),
m_socket(localPort),
m_debug(debug)
{
m_address = CUDPSocket::lookup(address);
CStopWatch stopWatch;
}
CUSRPNetwork::~CUSRPNetwork()
{
}
bool CUSRPNetwork::open()
{
LogMessage("USRP Network, Opening");
return m_socket.open();
}
void CUSRPNetwork::close()
{
m_socket.close();
}
uint32_t CUSRPNetwork::readData(uint8_t* data, uint32_t length)
{
in_addr address;
unsigned int port;
int len = m_socket.read(data, length, address, port);
if (len <= 0)
return 0U;
// Check if the data is for us
if (m_address.s_addr != address.s_addr || port != m_port) {
LogMessage("USRP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_port, port);
return 0U;
}
if (m_debug)
CUtils::dump(1U, "USRP Network Data Received", data, len);
return len;
}
bool CUSRPNetwork::writeData(const uint8_t* data, uint32_t length)
{
if (m_debug)
CUtils::dump(1U, "USRP Network Data Sent", data, length);
return m_socket.write(data, length, m_address, m_port);
}

47
USRP2P25/USRPNetwork.h Normal file
View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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.
*/
#if !defined(USRPNetwork_H)
#define USRPNetwork_H
#include "UDPSocket.h"
#include "Timer.h"
#include <string>
#include <cstdint>
class CUSRPNetwork
{
public:
CUSRPNetwork(const std::string& address, uint16_t dstPort, uint16_t localPort, bool debug);
~CUSRPNetwork();
uint32_t getConfig(uint8_t* config) const;
bool open();
bool writeData(const uint8_t* data, uint32_t length);
uint32_t readData(uint8_t* data, uint32_t length);
void close();
private:
in_addr m_address;
uint16_t m_port;
CUDPSocket m_socket;
bool m_debug;
};
#endif

146
USRP2P25/Utils.cpp Normal file
View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2009,2014,2015,2016 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; version 2 of the License.
*
* 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.
*/
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cassert>
void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length)
{
assert(data != NULL);
dump(2U, title, data, length);
}
void CUtils::dump(int level, const std::string& title, const unsigned char* data, unsigned int length)
{
assert(data != NULL);
::Log(level, "%s", title.c_str());
unsigned int offset = 0U;
while (length > 0U) {
std::string output;
unsigned int bytes = (length > 16U) ? 16U : length;
for (unsigned i = 0U; i < bytes; i++) {
char temp[10U];
::sprintf(temp, "%02X ", data[offset + i]);
output += temp;
}
for (unsigned int i = bytes; i < 16U; i++)
output += " ";
output += " *";
for (unsigned i = 0U; i < bytes; i++) {
unsigned char c = data[offset + i];
if (::isprint(c))
output += c;
else
output += '.';
}
output += '*';
::Log(level, "%04X: %s", offset, output.c_str());
offset += 16U;
if (length >= 16U)
length -= 16U;
else
length = 0U;
}
}
void CUtils::dump(const std::string& title, const bool* bits, unsigned int length)
{
assert(bits != NULL);
dump(2U, title, bits, length);
}
void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length)
{
assert(bits != NULL);
unsigned char bytes[100U];
unsigned int nBytes = 0U;
for (unsigned int n = 0U; n < length; n += 8U, nBytes++)
bitsToByteBE(bits + n, bytes[nBytes]);
dump(level, title, bytes, nBytes);
}
void CUtils::byteToBitsBE(unsigned char byte, bool* bits)
{
assert(bits != NULL);
bits[0U] = (byte & 0x80U) == 0x80U;
bits[1U] = (byte & 0x40U) == 0x40U;
bits[2U] = (byte & 0x20U) == 0x20U;
bits[3U] = (byte & 0x10U) == 0x10U;
bits[4U] = (byte & 0x08U) == 0x08U;
bits[5U] = (byte & 0x04U) == 0x04U;
bits[6U] = (byte & 0x02U) == 0x02U;
bits[7U] = (byte & 0x01U) == 0x01U;
}
void CUtils::byteToBitsLE(unsigned char byte, bool* bits)
{
assert(bits != NULL);
bits[0U] = (byte & 0x01U) == 0x01U;
bits[1U] = (byte & 0x02U) == 0x02U;
bits[2U] = (byte & 0x04U) == 0x04U;
bits[3U] = (byte & 0x08U) == 0x08U;
bits[4U] = (byte & 0x10U) == 0x10U;
bits[5U] = (byte & 0x20U) == 0x20U;
bits[6U] = (byte & 0x40U) == 0x40U;
bits[7U] = (byte & 0x80U) == 0x80U;
}
void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x01U : 0x00U;
byte |= bits[1U] ? 0x02U : 0x00U;
byte |= bits[2U] ? 0x04U : 0x00U;
byte |= bits[3U] ? 0x08U : 0x00U;
byte |= bits[4U] ? 0x10U : 0x00U;
byte |= bits[5U] ? 0x20U : 0x00U;
byte |= bits[6U] ? 0x40U : 0x00U;
byte |= bits[7U] ? 0x80U : 0x00U;
}

36
USRP2P25/Utils.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2009,2014,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; version 2 of the License.
*
* 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.
*/
#ifndef Utils_H
#define Utils_H
#include <string>
class CUtils {
public:
static void dump(const std::string& title, const unsigned char* data, unsigned int length);
static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length);
static void dump(const std::string& title, const bool* bits, unsigned int length);
static void dump(int level, const std::string& title, const bool* bits, unsigned int length);
static void byteToBitsBE(unsigned char byte, bool* bits);
static void byteToBitsLE(unsigned char byte, bool* bits);
static void bitsToByteBE(const bool* bits, unsigned char& byte);
static void bitsToByteLE(const bool* bits, unsigned char& byte);
private:
};
#endif

25
USRP2P25/Version.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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.
*/
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20210406";
#endif