From c76e26b0fc17c1e5cbc5c7004aa1ad2333a4205e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 20:43:57 +0100 Subject: [PATCH] #19 Add DGWTextTransmit --- .gitignore | 1 + .vscode/launch.json | 24 ++++ .vscode/tasks.json | 23 +++- DGWTextTransmit/Makefile | 22 ++++ DGWTextTransmit/TextTransmit.cpp | 206 +++++++++++++++++++++++++++++++ DGWTextTransmit/TextTransmit.h | 47 +++++++ Makefile | 6 +- README.md | 1 + 8 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 DGWTextTransmit/Makefile create mode 100644 DGWTextTransmit/TextTransmit.cpp create mode 100644 DGWTextTransmit/TextTransmit.h diff --git a/.gitignore b/.gitignore index 78fa9b1..8bf1885 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ dstargateway Tests/dstargateway_tests DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit +DGWTextTransmit/dgwtexttransmit diff --git a/.vscode/launch.json b/.vscode/launch.json index 7055ed5..cc81732 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,30 @@ } ] }, + { + "name": "(gdb) dgwtextransmit", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWTextTransmit/dgwtexttransmit", + "args": ["F4FXL B", "-text", "test de julien a nouveau"], + "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": "(gdb) dgwvoicetransmit", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d6bee3a..6066b4b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ "USE_GPSD=1", "all" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -42,6 +45,19 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWTextTransmit", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWTextTransmit/dgwtexttransmit" + ], + "group": "build", + "problemMatcher": [] + }, { "label": "Build DGWVoiceTransmit", "type": "shell", @@ -52,10 +68,7 @@ "USE_GPSD=1", "DGWVoiceTransmit/dgwvoicetransmit" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/DGWTextTransmit/Makefile b/DGWTextTransmit/Makefile new file mode 100644 index 0000000..d209f9e --- /dev/null +++ b/DGWTextTransmit/Makefile @@ -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: diff --git a/DGWTextTransmit/TextTransmit.cpp b/DGWTextTransmit/TextTransmit.cpp new file mode 100644 index 0000000..9862b1d --- /dev/null +++ b/DGWTextTransmit/TextTransmit.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 -text |-file , 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 namedArgs; + std::vector 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::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()); +} diff --git a/DGWTextTransmit/TextTransmit.h b/DGWTextTransmit/TextTransmit.h new file mode 100644 index 0000000..4b6ffd9 --- /dev/null +++ b/DGWTextTransmit/TextTransmit.h @@ -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 + +#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 diff --git a/Makefile b/Makefile index 8c33b90..7a131cb 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit 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 +DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWTextTransmit + DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit @@ -95,6 +98,7 @@ newhostfiles : install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol # install accessories $(MAKE) -C DGWRemoteControl install + $(MAKE) -C DGWTextTransmit install $(MAKE) -C DGWVoiceTransmit install # create user for daemon diff --git a/README.md b/README.md index 0a3a68d..b60fbde 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 - [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17))