diff --git a/README.md b/README.md index e2517ed9..2c691230 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,6 @@ ## FreeDV- Just Another TNC Experiment My first attempt to learn more about FreeDV and how to create a TNC which gets data from a TCP/IP socket - -## Credits - -David Rowe and the FreeDV team for developing the modem and libraries -FreeDV Codec 2 : https://github.com/drowe67/codec2 -This software has been heavily inspired by https://github.com/xssfox/freedv-tnc/ - ## ToDo - [x] ARQ: Stop-And-Wait @@ -16,17 +9,15 @@ This software has been heavily inspired by https://github.com/xssfox/freedv-tnc/ - [x] ARQ: Selective repeating of lost arq frames - [x] ARQ: Dynamic number of frames per burst - [ ] ARQ: Set frames per burst automatically by channel quality -- [ ] SETTING: Callsign with selective receiveing -- [ ] SETTING: Own grid locator -- [ ] SOCKET: Set settings & commands via command socket -- [ ] SOCKET: Receive data via data socket +- [x] SOCKET: Run commands via TCP/IP socket - [ ] TRX: Control radio via hamlib - [ ] MODE: Beacon - [ ] MODE: Broadcast - [ ] MODE: ARQ AX25 - [ ] MODE: Gear shifting ARQ - [ ] TNC: CLI GUI for basic settings - +- [ ] TNC: Multicore support +- [ ] MODEM: Sample rate conversion ## Setup Install FreeDV-Socket-TNC directly to home folder and compile codec2 automatically @@ -39,26 +30,73 @@ chmod +x ~/install_socket_tnc.sh ## Usage main program ``` -./main.py --port 3000 --tx 1 --rx 1 +./main.py --port 3000 --tx 1 --rx 1 --mode 12 ``` -## Usage testclient +## Usage TCP/IP socket client ``` -./socketclient.py --port 3000 --data "BC: hello" +python3 readfromsocket.py --port 3000 --data "GET:RX_BUFFER:0 ``` - ## Socket Commands -Send a simple broadcast +#### SOCKETTEST +Message for testing purposes which repeats: ``` -BC: +SOCKETTEST ``` -Send an ARQ like frame which will ask the receiver for acknowledgement +"WELL DONE! YOU ARE ABLE TO COMMUNICATE WITH THE TNC" + + +#### TRANSMIT ARQ MESSAGE 'HELLO!' ``` -ACK: +ARQ:HELLO! ``` +#### SET NEW CALLSIGN +``` +SET:MYCALLSIGN:AA1AA +``` + +#### GET CALLSIGN +``` +GET:MYCALLSIGN +``` + +#### GET CALLSIGN CRC8 +``` +GET:MYCALLSIGN_CRC8 +``` + +#### GET DX CALLSIGN +``` +GET:DXCALLSIGN +``` + +#### GET ARQ STATE +``` +GET:ARQ_STATE +``` + +#### GET RX BUFFER LENGTH / SIZE +``` +GET:RX_BUFFER_LENGTH +``` + +#### GET RX BUFFER +``` +GET:RX_BUFFER:POSITION +``` +Position = 0 --> Latest Data +Position 1-N --> Buffer positions + +#### DELETE RX BUFFER +``` +DEL:RX_BUFFER +``` + + + ## Other stuff @@ -76,3 +114,12 @@ sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 ./main.py --port 3001 --tx 2 --rx 2 ``` + +## Credits + +David Rowe and the FreeDV team for developing the modem and libraries +FreeDV Codec 2 : https://github.com/drowe67/codec2 + + +This software has been inspired by https://github.com/xssfox/freedv-tnc/ + diff --git a/arq.py b/arq.py index 74bb96c8..0be4b045 100644 --- a/arq.py +++ b/arq.py @@ -323,7 +323,7 @@ def transmit(data_out): #--------------- BREAK LOOP IF FRAME ACK HAS BEEN RECEIVED EARLIER AS EXPECTED elif static.ARQ_FRAME_ACK_RECEIVED == True: - logging.info("----------------------------------------------------------") + logging.info("ARQ | RX | EARLY FRAME ACK RECEIVED") #static.ARQ_N_SENT_FRAMES = #static.TX_BUFFER_SIZE @@ -363,11 +363,13 @@ def transmit(data_out): # ----------- if no ACK received and out of retries.....stop frame sending if static.ARQ_ACK_RECEIVED == False and static.ARQ_FRAME_ACK_RECEIVED == False and static.ARQ_RX_ACK_TIMEOUT == True: logging.error("ARQ | TX | NO BURST OR FRAME ACK RECEIVED | DATA SHOULD BE RESEND!") + logging.error("----------------------------------------------------------") break #-------------------------BREAK TX BUFFER LOOP IF ALL PACKETS HAVE BEEN SENT AND WE GOT A FRAME ACK elif static.ARQ_N_SENT_FRAMES == static.TX_BUFFER_SIZE and static.ARQ_FRAME_ACK_RECEIVED == True: logging.log(25,"ARQ | RX | FRAME ACK RECEIVED - DATA TRANSMITTED! :-)") + logging.log(25,"----------------------------------------------------------") break else: @@ -394,7 +396,7 @@ def transmit(data_out): # BURST MACHINE TO DEFINE N BURSTS PER FRAME ---> LATER WE CAN USE CHANNEL MESSUREMENT TO SET FRAMES PER BURST def get_n_frames_per_burst(): #n_frames_per_burst = randrange(1,10) - n_frames_per_burst = 2 + n_frames_per_burst = 5 return n_frames_per_burst diff --git a/main.py b/main.py index b6b19c85..c81c5dbe 100644 --- a/main.py +++ b/main.py @@ -12,8 +12,16 @@ import argparse import logging import threading + import static import helpers + +def client(ip, port, message): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((ip, port)) + sock.sendall(bytes(message, 'ascii')) + response = str(sock.recv(1024), 'ascii') + print("Received: {}".format(response)) if __name__ == '__main__': @@ -23,21 +31,6 @@ if __name__ == '__main__': # list audio devices helpers.list_audio_devices() - - - - static.MYCALLSIGN = b'DJ2LS' - static.MYCALLSIGN_CRC8 = helpers.get_crc_8(static.MYCALLSIGN) - - static.DXCALLSIGN = b'DH3WO' - static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN) - - print("MYCALLSIGN " + str(static.MYCALLSIGN)) - print("MYCALLSIGN_CRC8 " + str(static.MYCALLSIGN_CRC8)) - - print("DXCALLSIGN " + str(static.DXCALLSIGN)) - print("DXCALLSIGN_CRC8 " + str(static.DXCALLSIGN_CRC8)) - #--------------------------------------------GET PARAMETER INPUTS @@ -49,20 +42,16 @@ if __name__ == '__main__': args = parser.parse_args() - - #--------------------------------------------START CMD & DATA SERVER static.FREEDV_DATA_MODE = args.freedv_data_mode static.AUDIO_INPUT_DEVICE = args.audio_input_device static.AUDIO_OUTPUT_DEVICE = args.audio_output_device static.PORT = args.socket_port - + + #--------------------------------------------START CMD SERVER import sock # we need to wait until we got all parameters from argparse - + cmd_server_thread = threading.Thread(target=sock.start_cmd_socket, name="cmd server") cmd_server_thread.start() - - data_server_thread = threading.Thread(target=sock.start_data_socket, name="data server") - data_server_thread.start() - - + + diff --git a/readfromsocket.py b/readfromsocket.py new file mode 100644 index 00000000..ca8b525b --- /dev/null +++ b/readfromsocket.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 21:53:35 2020 + +@author: parallels +""" + +import socket +import sys +import argparse +import time + + + + +#--------------------------------------------GET PARAMETER INPUTS +parser = argparse.ArgumentParser(description='Simons TEST TNC') +parser.add_argument('--port', dest="socket_port", default=3000, help="Set the port, the socket is listening on.", type=int) +parser.add_argument('--data', dest="data", default=False, help="data", type=str) + +args = parser.parse_args() + +ip, port = "localhost", args.socket_port +message = args.data + + + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + print(ip) + print(port) + sock.connect((ip, port)) + sock.sendall(bytes(message, 'utf-8')) + response = str(sock.recv(1024), 'utf-8') + print("Received: {}".format(response)) diff --git a/sock.py b/sock.py index ebbc18f0..d1189de8 100644 --- a/sock.py +++ b/sock.py @@ -9,66 +9,107 @@ Created on Fri Dec 25 21:25:14 2020 import socketserver import threading import logging - +import time import static import arq +import helpers -class DATATCPRequestHandler(socketserver.BaseRequestHandler): +class CMDTCPRequestHandler(socketserver.BaseRequestHandler): def handle(self): - self.data = bytes() - while True: - chunk = self.request.recv(8192)#.strip() - self.data += chunk - if chunk.endswith(b'\n'): - break + encoding = 'utf-8' + data = str(self.request.recv(1024), 'utf-8') - # SEND AN ARQ FRAME ------------------------- - if self.data.startswith(b'ARQ:'): + # SOCKETTEST + if data == 'SOCKETTEST': + cur_thread = threading.current_thread() + response = bytes("WELL DONE! YOU ARE ABLE TO COMMUNICATE WITH THE TNC", encoding) + self.request.sendall(response) + + # TRANSMIT ARQ MESSAGE + if data.startswith('ARQ:'): + logging.info("CMD | NEW ARQ DATA") + + arqdata = data.split('ARQ:') + data_out = bytes(arqdata[1], 'utf-8') - data = self.data.split(b'ARQ:') - data_out = data[1] - TRANSMIT_ARQ = threading.Thread(target=arq.transmit, args=[data_out], name="TRANSMIT_ARQ") TRANSMIT_ARQ.start() - - + + + + # SETTINGS AND STATUS + if data.startswith('SET:MYCALLSIGN:'): + data = data.split('SET:MYCALLSIGN:') + if bytes(data[1], encoding) == b'': + self.request.sendall(b'INVALID CALLSIGN') + else: + static.MYCALLSIGN = bytes(data[1], encoding) + static.MYCALLSIGN_CRC8 = helpers.get_crc_8(static.MYCALLSIGN) + self.request.sendall(static.MYCALLSIGN) + logging.info("CMD | MYCALLSIGN: " + str(static.MYCALLSIGN)) + + + if data == 'GET:MYCALLSIGN': + self.request.sendall(bytes(static.MYCALLSIGN, encoding)) + + if data == 'GET:MYCALLSIGN_CRC8': + self.request.sendall(bytes(static.MYCALLSIGN_CRC8, encoding)) + + if data == 'GET:DXCALLSIGN': + self.request.sendall(bytes(static.DXCALLSIGN, encoding)) + + # ARQ + if data == 'GET:ARQ_STATE': + self.request.sendall(bytes(static.ARQ_STATE, encoding)) + + if data == 'GET:TX_N_MAX_RETRIES': + self.request.sendall(bytes([static.TX_N_MAX_RETRIES], encoding)) + + if data == 'GET:TX_N_RETRIES': + self.request.sendall(bytes([static.TX_N_RETRIES], encoding)) + + if data == 'GET:ARQ_TX_N_FRAMES_PER_BURST': + self.request.sendall(bytes([static.ARQ_TX_N_FRAMES_PER_BURST], encoding)) + + if data == 'GET:ARQ_TX_N_BURSTS': + self.request.sendall(bytes([static.ARQ_TX_N_BURSTS], encoding)) + + if data == 'GET:ARQ_TX_N_CURRENT_ARQ_FRAME': + self.request.sendall(bytes([static.ARQ_TX_N_CURRENT_ARQ_FRAME], encoding)) + + if data == 'GET:ARQ_TX_N_TOTAL_ARQ_FRAMES': + self.request.sendall(bytes([static.ARQ_TX_N_TOTAL_ARQ_FRAMES], encoding)) + + if data == 'GET:ARQ_RX_FRAME_N_BURSTS': + self.request.sendall(bytes([static.ARQ_RX_FRAME_N_BURSTS], encoding)) + + if data == 'GET:ARQ_RX_N_CURRENT_ARQ_FRAME': + self.request.sendall(bytes([static.ARQ_RX_N_CURRENT_ARQ_FRAME], encoding)) + + if data == 'GET:ARQ_N_ARQ_FRAMES_PER_DATA_FRAME': + self.request.sendall(bytes([static.ARQ_N_ARQ_FRAMES_PER_DATA_FRAME], encoding)) + + if data == 'GET:RX_BUFFER_LENGTH': + self.request.sendall(bytes(str(len(static.RX_BUFFER)),encoding)) -class CMDTCPRequestHandler(socketserver.BaseRequestHandler): + if data.startswith('GET:RX_BUFFER:'): + + data = data.split('GET:RX_BUFFER:') + bufferposition = int(data[1])-1 + if bufferposition == -1: + if len(static.RX_BUFFER) > 0: + self.request.sendall(static.RX_BUFFER[-1]) + + if bufferposition <= len(static.RX_BUFFER) > 0: + self.request.sendall(bytes(static.RX_BUFFER[bufferposition])) + - def handle(self): - - self.data = bytes() - while True: - chunk = self.request.recv(8192)#.strip() - self.data += chunk - if chunk.endswith(b'\n'): - break + if data == 'DEL:RX_BUFFER': + static.RX_BUFFER = [] - - - # self.request is the TCP socket connected to the client - #self.data = self.request.recv(1024).strip() -### self.data = self.request.recv(1000000).strip() - - # interrupt listening loop "while true" by setting MODEM_RECEIVE to False - #if len(self.data) > 0: - # static.MODEM_RECEIVE = False - - - ####print("{} wrote:".format(self.client_address[0])) - ####print(self.data) - - # just send back the same data, but upper-cased - #####self.request.sendall(self.data.upper()) - - #if self.data == b'TEST': - #logging.info("DER TEST KLAPPT! HIER KOMMT DER COMMAND PARSER HIN!") - if self.data.startswith(b'SHOWBUFFERSIZE'): - self.request.sendall(bytes(static.RX_BUFFER[-1])) - print(static.RX_BUFFER_SIZE) @@ -83,15 +124,3 @@ def start_cmd_socket(): finally: cmdserver.server_close() - - -def start_data_socket(): - - try: - logging.info("SRV | STARTING TCP/IP SOCKET FOR DATA ON PORT: " + str(static.PORT + 1)) - socketserver.TCPServer.allow_reuse_address = True #https://stackoverflow.com/a/16641793 - dataserver = socketserver.TCPServer((static.HOST, static.PORT + 1), DATATCPRequestHandler) - dataserver.serve_forever() - - finally: - dataserver.server_close() diff --git a/socketclient.py b/socketclient.py index 37141c73..6316c6d0 100644 --- a/socketclient.py +++ b/socketclient.py @@ -23,6 +23,7 @@ def create_string(length): # Keep appending random characters using chr(x) random_string += (chr(random_integer)) print("STR:" + str(random_string)) + return random_string @@ -42,9 +43,11 @@ args = parser.parse_args() data = create_string(args.datalength) -data = bytes("ACK:" + "!!!--" + data + "--!!!" + "\n", "utf-8") +data = bytes("ARQ:" + "" + data + "" + "\n", "utf-8") -print(data) + + +#print(data) HOST, PORT = "localhost", args.socket_port @@ -60,6 +63,6 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: received = str(sock.recv(1024), "utf-8") print("Sent: {}".format(data)) -print("Received: {}".format(received)) + diff --git a/static.py b/static.py index f1613d74..38350b70 100644 --- a/static.py +++ b/static.py @@ -7,11 +7,11 @@ Created on Wed Dec 23 11:13:57 2020 """ # Operator Defaults -MYCALLSIGN = b'' -MYCALLSIGN_CRC8 = b'' +MYCALLSIGN = b'AA0AA' +MYCALLSIGN_CRC8 = b'A' -DXCALLSIGN = b'' -DXCALLSIGN_CRC8 = b'' +DXCALLSIGN = b'AA0AA' +DXCALLSIGN_CRC8 = b'A' MYGRID = b'' #---------------------------------