From 4cc7ec561ca30ff6e52e7c49b112c89ff3b752a4 Mon Sep 17 00:00:00 2001 From: DJ2LS <75909252+DJ2LS@users.noreply.github.com> Date: Tue, 5 Jan 2021 15:03:41 +0100 Subject: [PATCH] first multimode support --- arq.py | 29 +++++-- audio.py | 46 +++++++++++ main.py | 39 +++++++++- modem.py | 198 ++++++++++++++++++++++++------------------------ socketclient.py | 35 ++++++++- static.py | 15 +++- 6 files changed, 249 insertions(+), 113 deletions(-) create mode 100644 audio.py diff --git a/arq.py b/arq.py index 440b9642..6250f8a0 100644 --- a/arq.py +++ b/arq.py @@ -20,7 +20,7 @@ import other from random import randrange -modem = modem.RF() +#modem = modem.RF() crc_algorithm = crcengine.new('crc16-ccitt-false') #load crc16 library @@ -29,6 +29,9 @@ static.ARQ_PAYLOAD_PER_FRAME = static.FREEDV_PAYLOAD_PER_FRAME - 3 #6?! +#DATA_RX_AUDIO_THREAD = threading.Thread(target=modem.Receive, args=[12], name="DATAC3 Listener") +#ACK_RX_AUDIO_THREAD = threading.Thread(target=modem.Receive, args=[7], name="700D Listener") + def data_received(data_in): ARQ_N_RX_BURSTS = int.from_bytes(bytes(data_in[:1]), "big") - 10 @@ -73,7 +76,7 @@ def data_received(data_in): ack_buffer[:len(ack_frame)] = ack_frame # set buffersize to length of data which will be send #TRANSMIT ACK FRAME ----------------------------------------------- - modem.Transmit(ack_buffer) + modem.Transmit(7,ack_buffer) static.ARQ_RX_BURST_BUFFER = [] # CLEAR RX BURST BUFFER @@ -96,7 +99,7 @@ def data_received(data_in): #if burst_total_payload[4:6].startswith(b'\xAA\xAA'): if complete_frame[4:6].startswith(b'\xAA\xAA') or burst_total_payload[4:6].startswith(b'\xAA\xAA'): - print("DAS IST DER ERSTE BURST MIT BOF!!!") + print("FRAME HEADER RECEIVED!") #print("FRAME BURSTS = " + str(complete_frame[:2])) #print("FRAME CRC = " + str(complete_frame[2:4])) static.FRAME_CRC = complete_frame[2:4] @@ -106,7 +109,7 @@ def data_received(data_in): if burst_total_payload.rstrip(b'\x00').endswith(b'\xFF\xFF'): print("DAS IST DER LETZTE BURST MIT EOF!!!") - + print("WEITER GEHTS") # NOW WE TRY TO SEPARATE THE FRAME CRC FOR A CRC CALCULATION frame_payload = complete_frame.rstrip(b'\x00') #REMOVE x00 frame_payload = frame_payload[6:-2] #THIS IS THE FRAME PAYLOAD @@ -264,7 +267,7 @@ def transmit(data_out): # ----------------------- Loop through ARQ FRAMES BUFFER with N = Numbers of frames which will be send at once for n in range(static.ARQ_TX_N_FRAMES): logging.info("TX | SENDING BURST [" + str(n+1) + " / " + str(static.ARQ_TX_N_FRAMES) + "] [" + str(static.ARQ_N_SENT_FRAMES + n+1) + " / " + str(static.TX_BUFFER_SIZE) + "] [" + str(burst_payload_crc) + "]") - modem.Transmit(arqburst[n]) + modem.Transmit(12, arqburst[n]) #LETS SLEEP SOME TIME FOR TX COOLDOWN --> CAN BE REMOVED LATER IF SYNC/UNSYNC OF FREEDV IS WORKING BETTER time.sleep(4) @@ -273,6 +276,11 @@ def transmit(data_out): timer = threading.Timer(static.ACK_TIMEOUT_SECONDS * static.ARQ_TX_N_FRAMES, other.timeout) timer.start() logging.info("TX | WAITING FOR ACK") + + #DATA_RX_AUDIO_THREAD.stop() + static.MODEM_RECEIVE = False + #time.sleep(1) + #ACK_RX_AUDIO_THREAD.start() # --------------------------- WHILE TIMEOUT NOT REACHED AND NO ACK RECEIVED --> LISTEN while static.ACK_TIMEOUT == 0 and static.ACK_RECEIVED == 0: @@ -280,10 +288,21 @@ def transmit(data_out): #else: #logging.info("TX | ACK TIMEOUT - SENDING AGAIN") #pass + #static.MODEM_RECEIVE = False + #time.sleep(1) + #ACK_RX_AUDIO_THREAD.start() #--------------- BREAK LOOP IF ACK HAS BEEN RECEIVED ######if static.ACK_RECEIVED == 1: if static.ACK_RECEIVED == 1: + + #ACK_RX_AUDIO_THREAD.stop() + static.MODEM_RECEIVE = False + #time.sleep(1) + #DATA_RX_AUDIO_THREAD.start() + + + #-----------IF ACK RECEIVED, INCREMENT ITERATOR FOR MAIN LOOP TO PROCEED WITH NEXT FRAMES/BURST static.ARQ_N_SENT_FRAMES = static.ARQ_N_SENT_FRAMES + static.ARQ_TX_N_FRAMES break diff --git a/audio.py b/audio.py new file mode 100644 index 00000000..2712d467 --- /dev/null +++ b/audio.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Sun Jan 3 10:12:05 2021 + +@author: DJ2LS +""" +import pyaudio + + +import static + + + + +def read_audio(): + + defaultFrames = static.DEFAULT_FRAMES + audio_input_device = static.AUDIO_INPUT_DEVICE + audio_output_device = static.AUDIO_OUTPUT_DEVICE + tx_sample_state = static.TX_SAMPLE_STATE + rx_sample_state = static.RX_SAMPLE_STATE + audio_sample_rate = static.AUDIO_SAMPLE_RATE + modem_sample_rate = static.MODEM_SAMPLE_RATE + audio_frames_per_buffer = static.AUDIO_FRAMES_PER_BUFFER + audio_channels = static.AUDIO_CHANNELS + format = pyaudio.paInt16 + stream = None + + n_max_modem_samples = 924 + + p = pyaudio.PyAudio() + + stream_rx = p.open(format=pyaudio.paInt16, + channels=6, + rate=8000, + frames_per_buffer=924, + input=True, + input_device_index=1, + ) + while True: + data_in = stream_rx.read(4096, exception_on_overflow = False) + static.AUDIO_BUFFER += data_in.strip(b'\x00') + + #print(static.AUDIO_BUFFER) + #print(len(static.AUDIO_BUFFER)) diff --git a/main.py b/main.py index aee9659b..a60a2e1a 100644 --- a/main.py +++ b/main.py @@ -16,6 +16,10 @@ import tnc import static import modem + +import audio + + modem = modem.RF() @@ -38,12 +42,39 @@ logger = logging.getLogger() logger.setLevel("INFO") #DEBUG>INFO>WARNING>ERROR>CRITICAL +#test_thread = threading.Thread(target=audio.read_audio, name="Audio Listener") +#test_thread.start() +#test_thread2 = threading.Thread(target=modem.play_audio, name="Audio Listener2") +#test_thread2.start() + #--------------------------------------------START AUDIO THREAD -logging.info("STARTING AUDIO THREAD") -static.MODEM_RECEIVE = True -audio_receiver_thread = threading.Thread(target=modem.Receive, name="Audio Listener") -audio_receiver_thread.start() +#logging.info("STARTING AUDIO THREAD") +#static.MODEM_RECEIVE = True +#audio_receiver_thread = threading.Thread(target=modem.Receive, name="Audio Listener") +#audio_receiver_thread.start() + +#--------------------------------------------START AUDIO THREAD + +#static.MODEM_RECEIVE = True + +logging.info("STARTING 700D RX THREAD") +FREEDV_700D_THREAD = threading.Thread(target=modem.Receive, args=[7], name="700D Listener") +FREEDV_700D_THREAD.start() + +#logging.info("STARTING DATAC1 RX THREAD") +#FREEDV_DATAC1_THREAD = threading.Thread(target=modem.Receive, args=[10], name="DATAC1 Listener") +#FREEDV_DATAC1_THREAD.start() + +#logging.info("STARTING DATAC2 RX THREAD") +#FREEDV_DATAC2_THREAD = threading.Thread(target=modem.Receive, args=[11], name="DATAC2 Listener") +#FREEDV_DATAC2_THREAD.start() + +logging.info("STARTING DATAC3 RX THREAD") +FREEDV_DATAC3_THREAD = threading.Thread(target=modem.Receive, args=[12], name="DATAC3 Listener") +FREEDV_DATAC3_THREAD.start() + + #--------------------------------------------START SERVER diff --git a/modem.py b/modem.py index 5a572a63..e955f94b 100644 --- a/modem.py +++ b/modem.py @@ -26,145 +26,144 @@ class RF(): def __init__(self): - self.p = pyaudio.PyAudio() - self.defaultFrames = static.DEFAULT_FRAMES - self.audio_input_device = static.AUDIO_INPUT_DEVICE - self.audio_output_device = static.AUDIO_OUTPUT_DEVICE - self.tx_sample_state = static.TX_SAMPLE_STATE - self.rx_sample_state = static.RX_SAMPLE_STATE - self.audio_sample_rate = static.AUDIO_SAMPLE_RATE - self.modem_sample_rate = static.MODEM_SAMPLE_RATE - self.audio_frames_per_buffer = static.AUDIO_FRAMES_PER_BUFFER - self.audio_channels = static.AUDIO_CHANNELS - self.format = pyaudio.paInt16 - self.stream = None + #self.p = pyaudio.PyAudio() + #self.defaultFrames = static.DEFAULT_FRAMES + #self.audio_input_device = static.AUDIO_INPUT_DEVICE + #self.audio_output_device = static.AUDIO_OUTPUT_DEVICE + #self.tx_sample_state = static.TX_SAMPLE_STATE + #self.rx_sample_state = static.RX_SAMPLE_STATE + #self.audio_sample_rate = static.AUDIO_SAMPLE_RATE + #self.modem_sample_rate = static.MODEM_SAMPLE_RATE + #self.audio_frames_per_buffer = static.AUDIO_FRAMES_PER_BUFFER + #self.audio_channels = static.AUDIO_CHANNELS + #self.format = pyaudio.paInt16 + #self.stream = None - #self.data_input = "stdin" - self.data_input = "audio" - #self.data_output = "stdout" - self.data_output = "audio" libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" self.c_lib = ctypes.CDLL(libname) - self.mode = static.FREEDV_MODE # define mode + #self.mode = static.FREEDV_MODE # define mode - self.freedv = self.c_lib.freedv_open(self.mode) - self.bytes_per_frame = int(self.c_lib.freedv_get_bits_per_modem_frame(self.freedv)/8) - self.payload_per_frame = self.bytes_per_frame -2 - self.n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(self.freedv)*2 #get n_tx_modem_samples which defines the size of the modulation object - self.n_max_modem_samples = self.c_lib.freedv_get_n_max_modem_samples(self.freedv) - self.n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.freedv) - self.nin = self.c_lib.freedv_nin(self.freedv) + #self.freedv = self.c_lib.freedv_open(self.mode) + #self.bytes_per_frame = int(self.c_lib.freedv_get_bits_per_modem_frame(self.freedv)/8) + #self.payload_per_frame = self.bytes_per_frame -2 + #self.n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(self.freedv)*2 #get n_tx_modem_samples which defines the size of the modulation object + #self.n_max_modem_samples = self.c_lib.freedv_get_n_max_modem_samples(self.freedv) + #self.n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.freedv) + #self.nin = self.c_lib.freedv_nin(self.freedv) - static.FREEDV_BYTES_PER_FRAME = self.bytes_per_frame - static.FREEDV_PAYLOAD_PER_FRAME = self.payload_per_frame + #static.FREEDV_BYTES_PER_FRAME = self.bytes_per_frame + #static.FREEDV_PAYLOAD_PER_FRAME = self.payload_per_frame + #print(static.AUDIO_INPUT_DEVICE) + # Open Audio Channel once + self.p = pyaudio.PyAudio() + self.stream_rx = self.p.open(format=pyaudio.paInt16, + channels=static.AUDIO_CHANNELS, + rate=static.AUDIO_SAMPLE_RATE, + frames_per_buffer=4096, + input=True, + input_device_index=static.AUDIO_INPUT_DEVICE, + ) - # MODULATION-OUT OBJECT - def ModulationOut(self): - return (c_short * self.n_tx_modem_samples) - - # MODULATION-IN OBJECT - def ModulationIn(self): - return (c_short * (self.n_max_modem_samples)) - # FRAME BYTES - # Pointer for changing buffer data type - def FrameBytes(self): - return (c_ubyte * self.bytes_per_frame) - # GET DATA AND MODULATE IT - def Transmit(self,data_out): - - mod_out = self.ModulationOut()() # new modulation object and get pointer to it + def Transmit(self,mode,data_out): + static.MODEM_RECEIVE = False + self.c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = self.c_lib.freedv_open(mode) + bytes_per_frame = int(self.c_lib.freedv_get_bits_per_modem_frame(freedv)/8) + payload_per_frame = bytes_per_frame -2 + n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(freedv) + n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(freedv)*2 #get n_tx_modem_samples which defines the size of the modulation object + + + mod_out = ctypes.c_short * n_tx_modem_samples + mod_out = mod_out() - data_list = [data_out[i:i+self.payload_per_frame] for i in range(0, len(data_out), self.payload_per_frame)] # split incomming bytes to size of 30bytes, create a list and loop through it + data_list = [data_out[i:i+payload_per_frame] for i in range(0, len(data_out), payload_per_frame)] # split incomming bytes to size of 30bytes, create a list and loop through it data_list_length = len(data_list) for i in range(data_list_length): # LOOP THROUGH DATA LIST - if self.mode < 10: # don't generate CRC16 for modes 0 - 9 + if mode < 10: # don't generate CRC16 for modes 0 - 9 - buffer = bytearray(self.bytes_per_frame) # use this if no CRC16 checksum is required + buffer = bytearray(bytes_per_frame) # use this if no CRC16 checksum is required buffer[:len(data_list[i])] = data_list[i] # set buffersize to length of data which will be send - if self.mode >= 10: #generate CRC16 for modes 10-12.. + if mode >= 10: #generate CRC16 for modes 10-12.. - buffer = bytearray(self.payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) + buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) buffer[:len(data_list[i])] = data_list[i] # set buffersize to length of data which will be send - crc = c_ushort(self.c_lib.freedv_gen_crc16(bytes(buffer), self.payload_per_frame)) # generate CRC16 + crc = ctypes.c_ushort(self.c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16 crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string buffer += crc # append crc16 to buffer - data = self.FrameBytes().from_buffer_copy(buffer) #change data format from bytearray to ctypes.u_byte and copy from buffer to data - - self.c_lib.freedv_rawdatatx(self.freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - if self.data_output == "stdout": - sys.stdout.buffer.write(mod_out) # print data to terminal for piping the output to other programs - sys.stdout.flush() # flushing stdout - - if self.data_output == "audio": - #print(self.audio_channels) - stream_tx = self.p.open(format=self.format, - channels=self.audio_channels, - rate=self.audio_sample_rate, - frames_per_buffer=self.n_nom_modem_samples, + self.c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer + + p = pyaudio.PyAudio() + stream_tx = p.open(format=pyaudio.paInt16, + channels=static.AUDIO_CHANNELS, + rate=static.AUDIO_SAMPLE_RATE, + frames_per_buffer=n_nom_modem_samples, output=True, - output_device_index=self.audio_output_device, + output_device_index=static.AUDIO_OUTPUT_DEVICE, ) - audio = audioop.ratecv(mod_out,2,1,self.modem_sample_rate, self.audio_sample_rate, self.tx_sample_state) - stream_tx.write(audio[0]) - stream_tx.close() + audio = audioop.ratecv(mod_out,2,1,static.MODEM_SAMPLE_RATE, static.AUDIO_SAMPLE_RATE, static.TX_SAMPLE_STATE) + stream_tx.write(audio[0]) + + + print("KILL") + stream_tx.stop_stream() + stream_tx.close() + p.terminate() return mod_out # DEMODULATE DATA AND RETURN IT - def Receive(self): + def Receive(self, mode): + static.MODEM_RECEIVE = True + + + + self.c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = self.c_lib.freedv_open(mode) + #n_max_modem_samples = self.c_lib.freedv_get_n_max_modem_samples(freedv) + bytes_per_frame = int(self.c_lib.freedv_get_bits_per_modem_frame(freedv)/8) + - # Open Audio Channel once - stream_rx = self.p.open(format=self.format, - channels=self.audio_channels, - rate=self.audio_sample_rate, - frames_per_buffer=self.n_max_modem_samples, - input=True, - input_device_index=self.audio_input_device, - ) - +##################################################################################################### + + bytes_out = (ctypes.c_ubyte * bytes_per_frame) + bytes_out = bytes_out() #get pointer from bytes_out + #i = 0 while static.MODEM_RECEIVE == True: # Listne to audio until data arrives - - #if self.data_input == "stdin": - # samples = self.c_lib.freedv_nin(self.freedv)*2 ### MIT DER *2 funktioniert das irgendwie recht zuverlässig bei mode 5! Bei Mode 12 auch - # data_in = sys.stdin.buffer.read(samples) - if self.data_input == "audio": - - data_in = stream_rx.read(self.c_lib.freedv_nin(self.freedv), exception_on_overflow = False) - #print(bytes(data_in)) - buffer = bytearray(self.n_max_modem_samples*2) # N MAX SAMPLES * 2 - buffer[:len(data_in)] = data_in # copy across what we have + nin = self.c_lib.freedv_nin(freedv) + data_in = self.stream_rx.read(nin, exception_on_overflow = False) + + nbytes = self.c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # Demodulated data and get number of demodulated bytes - self.ModulationIn()() #Create new ModulationIn Object - modulation = self.ModulationIn()# get an empty modulation array - modulation = modulation.from_buffer_copy(buffer) # copy buffer across and get a pointer to it. - - bytes_out = self.FrameBytes()() # initilize a pointer to where bytes will be outputed - - nbytes = self.c_lib.freedv_rawdatarx(self.freedv, bytes_out, data_in) # Demodulated data and get number of demodulated bytes - - if nbytes == self.bytes_per_frame: # make sure, we receive a full frame - - print(bytes(bytes_out[:-2])) - self.c_lib.freedv_set_sync(self.freedv, 0) #FORCE UNSYNC - + if nbytes == bytes_per_frame: # make sure, we receive a full frame + if mode >= 10: + + print("MODE: " + str(mode) + " DATA: " + str(bytes(bytes_out[:-2]))) + else: + print("MODE: " + str(mode) + " DATA: " + str(bytes(bytes_out))) + + + + self.c_lib.freedv_set_sync(freedv, 0) #FORCE UNSYNC # CHECK IF FRAMETYPE CONTAINS ACK------------------------ frametype = int.from_bytes(bytes(bytes_out[:1]), "big") @@ -180,4 +179,7 @@ class RF(): #return bytes(bytes_out[:-2]) - + #print("KILL") + #stream_rx.stop_stream() + #stream_rx.close() + #p.terminate() diff --git a/socketclient.py b/socketclient.py index f4106962..37141c73 100644 --- a/socketclient.py +++ b/socketclient.py @@ -9,30 +9,57 @@ Created on Fri Dec 11 21:53:35 2020 import socket import sys import argparse +import random +#https://www.askpython.com/python/examples/generate-random-strings-in-python +def create_string(length): + random_string = '' + for _ in range(length): + # Considering only upper and lowercase letters + random_integer = random.randint(97, 97 + 26 - 1) + flip_bit = random.randint(0, 1) + # Convert to lowercase if the flip bit is on + random_integer = random_integer - 32 if flip_bit == 1 else random_integer + # Keep appending random characters using chr(x) + random_string += (chr(random_integer)) + print("STR:" + str(random_string)) + return random_string + + + + #--------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description='Simons TEST TNC') parser.add_argument('--port', dest="socket_port", default=9000, help="Set the port, the socket is listening on.", type=int) -parser.add_argument('--data', dest="data", default=False, help="data", type=str) +#parser.add_argument('--data', dest="data", default=False, help="data", type=str) +parser.add_argument('--random', dest="datalength", default=False, help="data", type=int) + + + args = parser.parse_args() +data = create_string(args.datalength) +data = bytes("ACK:" + "!!!--" + data + "--!!!" + "\n", "utf-8") +print(data) HOST, PORT = "localhost", args.socket_port -data = args.data +#data = args.data # Create a socket (SOCK_STREAM means a TCP socket) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # Connect to server and send data sock.connect((HOST, PORT)) - sock.sendall(bytes(data + "\n", "utf-8")) - + #sock.sendall(bytes(data + "\n", "utf-8")) + sock.sendall(data) # Receive data from the server and shut down received = str(sock.recv(1024), "utf-8") print("Sent: {}".format(data)) print("Received: {}".format(received)) + + diff --git a/static.py b/static.py index 346d601b..413a3bc6 100644 --- a/static.py +++ b/static.py @@ -6,14 +6,23 @@ Created on Wed Dec 23 11:13:57 2020 @author: parallels """ + +#AUDIO_BUFFER = [] +#AUDIO_BUFFER = bytearray() +AUDIO_BUFFER = bytes() # Modem States MODEM_RECEIVE = True # FreeDV Defaults +TX_MODE = 12 FREEDV_MODE = 12 -FREEDV_BYTES_PER_FRAME = 0 -FREEDV_PAYLOAD_PER_FRAME = 0 + + + + +FREEDV_BYTES_PER_FRAME = 32 +FREEDV_PAYLOAD_PER_FRAME = 30 # Server Defaults HOST = "localhost" @@ -54,6 +63,8 @@ FRAME_CRC = b'' ARQ_N_SENT_FRAMES = 0 +ARQ_TX_ACK_MODE = 7 + # ------- TX BUFFER