diff --git a/.vscode/launch.json b/.vscode/launch.json index 5cb32b0..7055ed5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,30 @@ } ] }, + { + "name": "(gdb) dgwvoicetransmit", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit", + "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "Tests", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 46b9b3b..d6bee3a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -45,6 +42,22 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWVoiceTransmit", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWVoiceTransmit/dgwvoicetransmit" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, { "label": "Build Tests", "type": "shell", diff --git a/DGWVoiceTransmit/Makefile b/DGWVoiceTransmit/Makefile new file mode 100644 index 0000000..4579621 --- /dev/null +++ b/DGWVoiceTransmit/Makefile @@ -0,0 +1,22 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a + $(CC) $(CPPFLAGS) -o dgwvoicetransmit $(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 dgwvoicetransmit + +.PHONY install: +install: dgwvoicetransmit +# copy executable + @cp -f dgwvoicetransmit $(BIN_DIR) + +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../VersionInfo/GitVersion.h: diff --git a/DGWVoiceTransmit/VoiceStore.cpp b/DGWVoiceTransmit/VoiceStore.cpp new file mode 100644 index 0000000..7456eae --- /dev/null +++ b/DGWVoiceTransmit/VoiceStore.cpp @@ -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& 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(); +} diff --git a/DGWVoiceTransmit/VoiceStore.h b/DGWVoiceTransmit/VoiceStore.h new file mode 100644 index 0000000..670c9bb --- /dev/null +++ b/DGWVoiceTransmit/VoiceStore.h @@ -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 +#include + +#include "DVTOOLFileReader.h" +#include "HeaderData.h" +#include "AMBEData.h" + + +class CVoiceStore { +public: + CVoiceStore(const std::vector& filenames); + ~CVoiceStore(); + + bool open(); + + CHeaderData* getHeader(); + + CAMBEData* getAMBE(); + + void close(); + +private: + std::vector m_filenames; + CHeaderData* m_header; + unsigned int m_fileNumber; + CDVTOOLFileReader m_file; +}; + +#endif diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp new file mode 100644 index 0000000..d173241 --- /dev/null +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -0,0 +1,189 @@ +/* + * 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 +#include +#include +#include + +#include "DStarDefines.h" +#include "VoiceTransmit.h" + +int main(int argc, char** argv) +{ + std::string repeater; + std::vector filenames; + + if (!parseCLIArgs(argc, argv, repeater, filenames)) { + ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit ..., 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); + bool ret = tt.run(); + + store.close(); + + + return ret ? 0 : 1; +} + +bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& files) +{ + if(argc < 3) + return false; + + repeater.assign(argv[1]); + boost::to_upper(repeater); + boost::replace_all(repeater, "_", " "); + repeater.resize(LONG_CALLSIGN_LENGTH, ' '); + + files.clear(); + + for(int i = 2; i < argc; i++) { + if(argv[i] != nullptr) { + files.push_back(std::string(argv[i])); + } + } + + return files.size() > 0U; +} + +CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store) : +m_socket("", 0U), +m_callsign(callsign), +m_store(store) +{ + assert(store != NULL); +} + +CVoiceTransmit::~CVoiceTransmit() +{ +} + +bool CVoiceTransmit::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 = 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); + + sendHeader(header); + + delete header; + + auto start = std::chrono::high_resolution_clock::now(); + + unsigned int out = 0U; + unsigned int seqNo = 0U; + + for (;;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + CAMBEData* ambe = m_store->getAMBE(); + + if (ambe == NULL) { + seqNo++; + if (seqNo >= 21U) + seqNo = 0U; + + 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(); + + return true; + } + + seqNo = ambe->getSeq(); + + ambe->setDestination(address, G2_DV_PORT); + ambe->setEnd(false); + ambe->setId(id); + + sendData(ambe); + + delete ambe; + + out++; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10U)); + } +} + +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()); +} diff --git a/DGWVoiceTransmit/VoiceTransmit.h b/DGWVoiceTransmit/VoiceTransmit.h new file mode 100644 index 0000000..3a72384 --- /dev/null +++ b/DGWVoiceTransmit/VoiceTransmit.h @@ -0,0 +1,49 @@ +/* + * 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 +#include + +#include "UDPReaderWriter.h" +#include "VoiceStore.h" +#include "HeaderData.h" +#include "AMBEData.h" + +bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& vector); + +class CVoiceTransmit { +public: + CVoiceTransmit(const std::string& callsign, CVoiceStore* store); + ~CVoiceTransmit(); + + bool run(); + +private: + CUDPReaderWriter m_socket; + std::string m_callsign; + CVoiceStore* m_store; + + bool sendHeader(CHeaderData* header); + bool sendData(CAMBEData* data); +}; + +#endif diff --git a/Makefile b/Makefile index cb395a8..8c33b90 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit #tests APRS/APRS.a: BaseCommon/BaseCommon.a FORCE $(MAKE) -C APRS @@ -60,6 +60,9 @@ DStarGateway/dstargateway : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWRemoteControl +DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWVoiceTransmit + IRCDDB/IRCDDB.a: VersionInfo/GitVersion.h BaseCommon/BaseCommon.a FORCE $(MAKE) -C IRCDDB @@ -90,8 +93,10 @@ newhostfiles : .PHONY: install install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol -# install remote control +# install accessories $(MAKE) -C DGWRemoteControl install + $(MAKE) -C DGWVoiceTransmit install + # create user for daemon @useradd --user-group -M --system dstar --shell /bin/false || true