diff --git a/README.md b/README.md index 32069da0..acee05bb 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,7 @@ # FreeDV-JATE -## Just Another TNC Experiment - +## 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 -## Preview - -![alt text](https://github.com/DJ2LS/FreeDV-JATE/blob/main/documentation/freedv_jate_tnc_preview.png "Preview") - - - - - ## Credits @@ -20,3 +11,34 @@ FreeDV Codec 2 : https://github.com/drowe67/codec2 This software has been heavily inspired by https://github.com/xssfox/freedv-tnc/ + + + + + +## Setup +Install FreeDV-Socket-TNC directly to home folder and compile codec2 automatically +``` +sudo apt install python3-pyaudio build-essential cmake +python3 -m pip install crcengine ( if stuck in installing, run as superuser - sudo ) +sudo adduser $USER dialout +wget https://raw.githubusercontent.com/DJ2LS/FreeDV-Socket-TNC/dev/install_socket_tnc.sh -O ~/install_socket_tnc.sh +chmod +x ~/install_socket_tnc.sh +./install_socket_tnc.sh +``` +## List audio interfaces +``` +cd ~/FreeDV-JATE +python3 tools/list_audio_devices.py +``` + +## Usage main program +``` +python3 main.py --rx 1 --tx 1 --deviceport /dev/ttyUSB0 --deviceid 311 +``` + +## Usage GUI +``` +cd tools/tnc_gui +python3 tnc_gui.py +``` diff --git a/daemon.py b/daemon.py new file mode 100644 index 00000000..8f6a28af --- /dev/null +++ b/daemon.py @@ -0,0 +1,187 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Dec 22 16:58:45 2020 + +@author: DJ2LS + +""" + + +import argparse +import threading +import socketserver +import pyaudio +import time +import json +import subprocess +import os +import static + +#PORT = 3001 +#TNCPROCESS = 0 +#TNCSTARTED = False + +#p = pyaudio.PyAudio() +#info = p.get_host_api_info_by_index(0) +#numdevices = info.get('deviceCount') +#for each audio device, determine if is an input or an output and add it to the appropriate list and dictionary +#for i in range (0,numdevices): +# if p.get_device_info_by_host_api_device_index(0,i).get('maxInputChannels')>0: +# print("Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0,i).get('name'))# +# +# if p.get_device_info_by_host_api_device_index(0,i).get('maxOutputChannels')>0: +# print("Output Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0,i).get('name')) + +def start_daemon(): + + try: + print("SRV | STARTING TCP/IP SOCKET FOR CMD ON PORT: " + str(PORT)) + socketserver.TCPServer.allow_reuse_address = True # https://stackoverflow.com/a/16641793 + daemon = socketserver.TCPServer(('0.0.0.0', PORT), CMDTCPRequestHandler) + daemon.serve_forever() + + finally: + daemon.server_close() + + +class CMDTCPRequestHandler(socketserver.BaseRequestHandler): + + def handle(self): + print("Client connected...") + + # loop through socket buffer until timeout is reached. then close buffer + socketTimeout = time.time() + 3 + while socketTimeout > time.time(): + + time.sleep(0.01) + encoding = 'utf-8' + #data = str(self.request.recv(1024), 'utf-8') + + data = bytes() + + # we need to loop through buffer until end of chunk is reached or timeout occured + while True and socketTimeout > time.time(): + chunk = self.request.recv(1024) # .strip() + data += chunk + if chunk.endswith(b'\n'): + break + data = data[:-1] # remove b'\n' + data = str(data, 'utf-8') + #print(data) + + if len(data) > 0: + socketTimeout = time.time() + 3 + + # convert data to json object + # we need to do some error handling in case of socket timeout + + try: + received_json = json.loads(data) + + except: + received_json = '' + + + + # GET COMMANDS + # "command" : "..." + + # SET COMMANDS + # "command" : "..." + # "parameter" : " ..." + + # DATA COMMANDS + # "command" : "..." + # "type" : "..." + # "dxcallsign" : "..." + # "data" : "..." + + # print(received_json) + #print(received_json["type"]) + #print(received_json["command"]) + try: + print(static.TNCSTARTED) + + + + + if received_json["type"] == 'SET' and received_json["command"] == 'STARTTNC' and not static.TNCSTARTED: + rx_audio = received_json["parameter"][0]["rx_audio"] + tx_audio = received_json["parameter"][0]["tx_audio"] + deviceid = received_json["parameter"][0]["deviceid"] + deviceport = received_json["parameter"][0]["deviceport"] + ptt = received_json["parameter"][0]["ptt"] + print("STARTING TNC !!!!!") + print(received_json["parameter"][0]) + #os.system("python3 main.py --rx 3 --tx 3 --deviceport /dev/ttyUSB0 --deviceid 2028") + p = subprocess.Popen("exec python3 main.py --rx "+ str(rx_audio) +" --tx "+ str(tx_audio) +" --deviceport "+ str(deviceport) +" --deviceid "+ str(deviceid) + " --ptt "+ str(ptt), shell=True) + static.TNCPROCESS = p#.pid + #print(parameter) + # print(static.TNCPROCESS) + static.TNCSTARTED = True + + if received_json["type"] == 'SET' and received_json["command"] == 'STOPTNC': + parameter = received_json["parameter"] + static.TNCPROCESS.kill() + print("KILLING PROCESS ------------") + #os.kill(static.TNCPROCESS, signal.SIGKILL) + static.TNCSTARTED = False + + if received_json["type"] == 'GET' and received_json["command"] == 'DAEMON_STATE': + + data = {'COMMAND' : 'DAEMON_STATE', 'DAEMON_STATE' : [], 'INPUT_DEVICES': [], 'OUTPUT_DEVICES': []} + + if static.TNCSTARTED: + data["DAEMON_STATE"].append({"STATUS": "running"}) + else: + data["DAEMON_STATE"].append({"STATUS": "stopped"}) + + p = pyaudio.PyAudio() + for i in range(0, p.get_device_count()): + + maxInputChannels = p.get_device_info_by_host_api_device_index(0,i).get('maxInputChannels') + maxOutputChannels = p.get_device_info_by_host_api_device_index(0,i).get('maxOutputChannels') + name = p.get_device_info_by_host_api_device_index(0,i).get('name') + + if maxInputChannels > 0: + data["INPUT_DEVICES"].append({"ID": i, "NAME" : name}) + if maxOutputChannels > 0: + data["OUTPUT_DEVICES"].append({"ID": i, "NAME" : name}) + + + + #print(data) + jsondata = json.dumps(data) + self.request.sendall(bytes(jsondata, encoding)) + + + + #exception, if JSON cant be decoded + except Exception as e: + print('PROGRAM ERROR: %s' %str(e)) + print("Wrong command") + + print("Client disconnected...") + + +if __name__ == '__main__': + + + # --------------------------------------------GET PARAMETER INPUTS + PARSER = argparse.ArgumentParser(description='Simons TEST TNC') + PARSER.add_argument('--port', dest="socket_port", default=3001, help="Socket port", type=int) + + ARGS = PARSER.parse_args() + PORT = ARGS.socket_port + + # --------------------------------------------START CMD SERVER + + DAEMON_THREAD = threading.Thread(target=start_daemon, name="daemon") + DAEMON_THREAD.start() + + + + + + diff --git a/data_handler.py b/data_handler.py index c5a13a1a..79b99c28 100644 --- a/data_handler.py +++ b/data_handler.py @@ -523,10 +523,6 @@ def open_dc_and_transmit(data_out, mode, n_frames): #on a new transmission we reset the timer static.ARQ_START_OF_TRANSMISSION = int(time.time()) - # lets wait a little bit so RX station is ready for receiving - wait_before_data_timer = time.time() + 0.5 - while time.time() < wait_before_data_timer: - pass # lets wait a little bit #time.sleep(5) diff --git a/helpers.py b/helpers.py new file mode 100644 index 00000000..de273f66 --- /dev/null +++ b/helpers.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 25 21:25:14 2020 + +@author: DJ2LS +""" + +import time +import logging +import asyncio +import crcengine + + +import static +import data_handler + + +def get_crc_8(data): + """ + Author: DJ2LS + + Get the CRC8 of a byte string + + param: data = bytes() + """ + crc_algorithm = crcengine.new('crc8-ccitt') # load crc8 library + crc_data = crc_algorithm(data) + crc_data = crc_data.to_bytes(1, byteorder='big') + return crc_data + + +def get_crc_16(data): + """ + Author: DJ2LS + + Get the CRC16 of a byte string + + param: data = bytes() + """ + crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc16 library + crc_data = crc_algorithm(data) + crc_data = crc_data.to_bytes(2, byteorder='big') + return crc_data + +def watchdog(): + """ + Author: DJ2LS + + watchdog master function. Frome here we call the watchdogs + """ + while True: + time.sleep(0.01) + data_channel_keep_alive_watchdog() + +def data_channel_keep_alive_watchdog(): + """ + Author: DJ2LS + + + """ + + if static.ARQ_STATE == 'DATA' and static.TNC_STATE == 'BUSY': # and not static.ARQ_SEND_KEEP_ALIVE: + time.sleep(0.01) + if static.ARQ_DATA_CHANNEL_LAST_RECEIVED + 30 > time.time(): + pass + else: + static.ARQ_DATA_CHANNEL_LAST_RECEIVED = 0 + logging.info("DATA [" + str(static.MYCALLSIGN, 'utf-8') + "]<>[" + str(static.DXCALLSIGN, 'utf-8') + "] [BER." + str(static.BER) + "]") + arq_reset_frame_machine() + + +def arq_reset_timeout(state): + """ + Author: DJ2LS + """ + static.ARQ_RX_ACK_TIMEOUT = state + static.ARQ_RX_FRAME_TIMEOUT = state + static.ARQ_RX_RPT_TIMEOUT = state + + +def arq_reset_ack(state): + """ + Author: DJ2LS + """ + static.ARQ_ACK_RECEIVED = state + static.ARQ_RPT_RECEIVED = state + static.ARQ_FRAME_ACK_RECEIVED = state + + +def arq_reset_frame_machine(): + """ + Author: DJ2LS + + Reset the frame machine parameters to default, + so we need to call just a function + + """ + arq_reset_timeout(False) + arq_reset_ack(False) + static.TX_N_RETRIES = 0 + static.ARQ_N_SENT_FRAMES = 0 + static.ARQ_TX_N_FRAMES_PER_BURST = 0 + static.ARQ_TX_N_CURRENT_ARQ_FRAME = 0 + static.ARQ_TX_N_TOTAL_ARQ_FRAMES = 0 + static.ARQ_TX_N_CURRENT_ARQ_FRAME = 0 + + static.ARQ_RX_N_CURRENT_ARQ_FRAME = 0 + static.ARQ_N_ARQ_FRAMES_PER_DATA_FRAME = 0 + static.ARQ_FRAME_BOF_RECEIVED = False + static.ARQ_FRAME_EOF_RECEIVED = False + + static.ARQ_RX_BURST_BUFFER = [] + static.ARQ_RX_FRAME_BUFFER = [] + + static.TNC_STATE = 'IDLE' + static.ARQ_STATE = 'IDLE' + ###static.ARQ_CONNECTION_KEEP_ALIVE_RECEIVED = int(time.time()) # we need to reset the counter at this point + ###static.ARQ_SEND_KEEP_ALIVE = True + static.CHANNEL_STATE = 'RECEIVING_SIGNALLING' + static.ARQ_READY_FOR_DATA = False + + static.ARQ_START_OF_TRANSMISSION = 0 + +def calculate_transfer_rate(): + + if static.ARQ_TX_N_TOTAL_ARQ_FRAMES == 0: + total_n_frames = static.ARQ_N_ARQ_FRAMES_PER_DATA_FRAME + elif static.ARQ_N_ARQ_FRAMES_PER_DATA_FRAME == 0: + total_n_frames = int.from_bytes(static.ARQ_TX_N_TOTAL_ARQ_FRAMES, "big") + + + total_bytes = (total_n_frames * static.ARQ_PAYLOAD_PER_FRAME) + total_transmission_time = time.time() - static.ARQ_START_OF_TRANSMISSION + + burst_bytes = static.ARQ_PAYLOAD_PER_FRAME + burst_transmission_time = time.time() - static.ARQ_START_OF_BURST + + static.ARQ_BITS_PER_SECOND_TRANSMISSION = int((total_bytes * 8) / total_transmission_time) + static.ARQ_BYTES_PER_MINUTE_TRANSMISSION = int(((total_bytes) / total_transmission_time) * 60) + + static.ARQ_BITS_PER_SECOND_BURST = int((burst_bytes * 8) / burst_transmission_time) + static.ARQ_BYTES_PER_MINUTE_BURST = int(((burst_bytes) / burst_transmission_time) * 60) + + + return [static.ARQ_BITS_PER_SECOND_TRANSMISSION, static.ARQ_BYTES_PER_MINUTE_TRANSMISSION, static.ARQ_BITS_PER_SECOND_BURST, static.ARQ_BYTES_PER_MINUTE_BURST] + + + + + +def add_to_heard_stations(dxcallsign,dxgrid, datatype): + # check if buffer empty + if len(static.HEARD_STATIONS) == 0: + static.HEARD_STATIONS.append([dxcallsign,dxgrid, int(time.time()), datatype]) + # if not, we search and update + else: + for i in range(0, len(static.HEARD_STATIONS)): + # update callsign with new timestamp + if static.HEARD_STATIONS[i].count(dxcallsign) > 0: + static.HEARD_STATIONS[i] = [dxcallsign,dxgrid, int(time.time()), datatype] + break + # insert if nothing found + if i == len(static.HEARD_STATIONS) - 1: + static.HEARD_STATIONS.append([dxcallsign,dxgrid, int(time.time()), datatype]) + break + + +# for idx, item in enumerate(static.HEARD_STATIONS): +# if dxcallsign in item: +# item = [dxcallsign, int(time.time())] +# static.HEARD_STATIONS[idx] = item + +def setup_logging(): + """ + Author: DJ2LS + + Set the custom logging format so we can use colors + + # https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output + # 'DEBUG' : 37, # white + # 'INFO' : 36, # cyan + # 'WARNING' : 33, # yellow + # 'ERROR' : 31, # red + # 'CRITICAL': 41, # white on red bg + + """ + + logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s', datefmt='%H:%M:%S', level=logging.INFO) + + logging.addLevelName(logging.DEBUG, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) + logging.addLevelName(logging.INFO, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.INFO)) + logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) + logging.addLevelName(logging.ERROR, "\033[1;31m%s\033[1;0m" % "FAILED") + #logging.addLevelName( logging.ERROR, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) + logging.addLevelName(logging.CRITICAL, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL)) + + logging.addLevelName(25, "\033[1;32m%s\033[1;0m" % "SUCCESS") + logging.addLevelName(24, "\033[1;34m%s\033[1;0m" % "DATA") + + diff --git a/main.py b/main.py new file mode 100644 index 00000000..6eae31d3 --- /dev/null +++ b/main.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Dec 22 16:58:45 2020 + +@author: DJ2LS + +""" + + +import argparse +import threading +import static + + + +if __name__ == '__main__': + + + # --------------------------------------------GET PARAMETER INPUTS + PARSER = argparse.ArgumentParser(description='Simons TEST TNC') + PARSER.add_argument('--rx', dest="audio_input_device", default=0, help="listening sound card", type=int) + PARSER.add_argument('--tx', dest="audio_output_device", default=0, help="transmitting sound card", type=int) + PARSER.add_argument('--port', dest="socket_port", default=3000, help="Socket port", type=int) + PARSER.add_argument('--deviceport', dest="hamlib_device_port", default="/dev/ttyUSB0", help="Socket port", type=str) + PARSER.add_argument('--deviceid', dest="hamlib_device_id", default=3011, help="Socket port", type=int) + PARSER.add_argument('--ptt', dest="hamlib_ptt_type", default='RTS', help="PTT Type", type=str) + + + ARGS = PARSER.parse_args() + + static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device + static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device + static.PORT = ARGS.socket_port + static.HAMLIB_DEVICE_ID = ARGS.hamlib_device_id + static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port + static.HAMLIB_PTT_TYPE = ARGS.hamlib_ptt_type + + + # we need to wait until we got all parameters from argparse first before we can load the other modules + import sock + import helpers + + + + # config logging + helpers.setup_logging() + + # --------------------------------------------START CMD SERVER + + CMD_SERVER_THREAD = threading.Thread(target=sock.start_cmd_socket, name="cmd server") + CMD_SERVER_THREAD.start() + + WATCHDOG_SERVER_THREAD = threading.Thread(target=helpers.watchdog, name="watchdog") + WATCHDOG_SERVER_THREAD.start() diff --git a/modem.py b/modem.py new file mode 100644 index 00000000..287d9b1b --- /dev/null +++ b/modem.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 07:04:24 2020 + +@author: DJ2LS +""" + +import ctypes +from ctypes import * +import pathlib +import pyaudio +import audioop +import asyncio +#import sys +import logging +import time +import threading + +import helpers +import static +import data_handler + +import Hamlib + + + + +# test +import numpy as np +from scipy.fft import fft, ifft +from scipy import signal + +class RF(): + + def __init__(self): + + # -------------------------------------------- LOAD FREEDV + libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" + self.c_lib = ctypes.CDLL(libname) + # --------------------------------------------CREATE PYAUDIO INSTANCE + self.p = pyaudio.PyAudio() + # --------------------------------------------OPEN AUDIO CHANNEL RX + self.stream_rx = self.p.open(format=pyaudio.paInt16, + channels=static.AUDIO_CHANNELS, + rate=static.AUDIO_SAMPLE_RATE_RX, + frames_per_buffer=static.AUDIO_FRAMES_PER_BUFFER, + input=True, + input_device_index=static.AUDIO_INPUT_DEVICE + ) + # --------------------------------------------OPEN AUDIO CHANNEL TX + self.stream_tx = self.p.open(format=pyaudio.paInt16, + channels=1, + rate=static.AUDIO_SAMPLE_RATE_TX, + frames_per_buffer=static.AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples + output=True, + output_device_index=static.AUDIO_OUTPUT_DEVICE, # static.AUDIO_OUTPUT_DEVICE + ) + + self.streambuffer = bytes(0) + self.audio_writing_to_stream = False + # --------------------------------------------START DECODER THREAD + FREEDV_DECODER_THREAD_10 = threading.Thread(target=self.receive, args=[10], name="FREEDV_DECODER_THREAD_10") + FREEDV_DECODER_THREAD_10.start() + + #FREEDV_DECODER_THREAD_11 = threading.Thread(target=self.receive, args=[11], name="FREEDV_DECODER_THREAD_11") + #FREEDV_DECODER_THREAD_11.start() + + FREEDV_DECODER_THREAD_12 = threading.Thread(target=self.receive, args=[12], name="FREEDV_DECODER_THREAD_12") + FREEDV_DECODER_THREAD_12.start() + + FREEDV_DECODER_THREAD_14 = threading.Thread(target=self.receive, args=[static.FREEDV_SIGNALLING_MODE], name="FREEDV_DECODER_THREAD_14") + FREEDV_DECODER_THREAD_14.start() + + FREEDV_PLAYBACK_THREAD = threading.Thread(target=self.play_audio, name="FREEDV_DECODER_THREAD_14") + FREEDV_PLAYBACK_THREAD.start() + + # --------------------------------------------CONFIGURE HAMLIB + + + # try to init hamlib + try: + Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE) + + self.my_rig = Hamlib.Rig(static.HAMLIB_DEVICE_ID) + self.my_rig.set_conf("rig_pathname", static.HAMLIB_DEVICE_PORT) + + self.my_rig.set_conf("retry", "5") + self.my_rig.set_conf("serial_speed", "9600") + + #self.my_rig.set_conf("dtr_state", "OFF") + #my_rig.set_conf("rts_state", "OFF") + #self.my_rig.set_conf("ptt_type", "RTS") + #my_rig.set_conf("ptt_type", "RIG_PTT_SERIAL_RTS") + + self.my_rig.set_conf("serial_handshake", "None") + self.my_rig.set_conf("stop_bits", "1") + self.my_rig.set_conf("data_bits", "8") + + #my_rig.set_ptt(Hamlib.RIG_PTT_RIG,0) + #my_rig.set_ptt(Hamlib.RIG_PTT_SERIAL_DTR,0) + #my_rig.set_ptt(Hamlib.RIG_PTT_SERIAL_RTS,1) + + if static.HAMLIB_PTT_TYPE == 'RIG_PTT_RIG': + self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG + + elif static.HAMLIB_PTT_TYPE == 'RIG_PTT_SERIAL_DTR': + self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR + + elif static.HAMLIB_PTT_TYPE == 'RTS': + self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_RTS + self.my_rig.set_conf("dtr_state", "OFF") + self.my_rig.set_conf("ptt_type", "RTS") + + elif static.HAMLIB_PTT_TYPE == 'RIG_PTT_PARALLEL': + self.hamlib_ptt_type = Hamlib.RIG_PTT_PARALLEL + + elif static.HAMLIB_PTT_TYPE == 'RIG_PTT_RIG_MICDATA': + self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG_MICDATA + + elif static.HAMLIB_PTT_TYPE == 'RIG_PTT_CM108': + self.hamlib_ptt_type = Hamlib.RIG_PTT_CM108 + + else: # static.HAMLIB_PTT_TYPE == 'RIG_PTT_NONE': + self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE + + + self.my_rig.open() + + except: + print("can't open rig") + + +# -------------------------------------------------------------------------------------------------------- + def ptt_and_wait(self, state): + + if state: + static.PTT_STATE = True + self.my_rig.set_ptt(self.hamlib_ptt_type, 1) + + ptt_togle_timeout = time.time() + 0.1 + while time.time() < ptt_togle_timeout: + pass + + else: + + ptt_togle_timeout = time.time() + 0.2 + while time.time() < ptt_togle_timeout: + pass + + static.PTT_STATE = False + self.my_rig.set_ptt(self.hamlib_ptt_type, 0) + + + + + def play_audio(self): + + while True: + time.sleep(0.01) + + while len(self.streambuffer) > 0: + time.sleep(0.01) + if len(self.streambuffer) > 0: + self.audio_writing_to_stream = True + self.streambuffer = bytes(self.streambuffer) + + # we need t wait a little bit until the buffer is filled. If we are not waiting, we are sending empty data + time.sleep(0.1) + self.stream_tx.write(self.streambuffer) + self.streambuffer = bytes() + + self.audio_writing_to_stream = False +# -------------------------------------------------------------------------------------------------------- + + def transmit_signalling(self, data_out): + + self.c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = self.c_lib.freedv_open(static.FREEDV_SIGNALLING_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) # get n_tx_modem_samples which defines the size of the modulation object + n_tx_preamble_modem_samples = self.c_lib.freedv_get_n_tx_preamble_modem_samples(freedv) + n_tx_postamble_modem_samples = self.c_lib.freedv_get_n_tx_postamble_modem_samples(freedv) + + mod_out = ctypes.c_short * n_tx_modem_samples + mod_out = mod_out() + + mod_out_preamble = ctypes.c_short * n_tx_preamble_modem_samples # *2 #1760 for mode 10,11,12 #4000 for mode 9 + mod_out_preamble = mod_out_preamble() + + mod_out_postamble = ctypes.c_short * n_tx_postamble_modem_samples # *2 #1760 for mode 10,11,12 #4000 for mode 9 + mod_out_postamble = mod_out_postamble() + + buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) + buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send + + 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 = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) + + self.c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) + self.c_lib.freedv_rawdatatx(freedv, mod_out, data) # modulate DATA and safe it into mod_out pointer + self.c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) + + self.streambuffer = bytearray() + self.streambuffer += bytes(mod_out_preamble) + self.streambuffer += bytes(mod_out) + self.streambuffer += bytes(mod_out_postamble) + + converted_audio = audioop.ratecv(self.streambuffer,2,1,static.MODEM_SAMPLE_RATE, static.AUDIO_SAMPLE_RATE_TX, None) + self.streambuffer = bytes(converted_audio[0]) + + # -------------- transmit audio + #logging.debug("SENDING SIGNALLING FRAME " + str(data_out)) + + state_before_transmit = static.CHANNEL_STATE + static.CHANNEL_STATE = 'SENDING_SIGNALLING' + + self.ptt_and_wait(True) + self.audio_writing_to_stream = True + + # wait until audio has been processed + while self.audio_writing_to_stream: + time.sleep(0.01) + static.CHANNEL_STATE = 'SENDING_SIGNALLING' + + self.ptt_and_wait(False) + static.CHANNEL_STATE = state_before_transmit + + self.c_lib.freedv_close(freedv) + +# -------------------------------------------------------------------------------------------------------- + # GET ARQ BURST FRAME VOM BUFFER AND MODULATE IT + + def transmit_arq_burst(self): + + # we could place this timing part inside the modem... + # lets see if this is a good idea.. + static.ARQ_DATA_CHANNEL_LAST_RECEIVED = int(time.time()) # we need to update our timeout timestamp + static.ARQ_START_OF_BURST = int(time.time()) # we need to update our timeout timestamp + + self.my_rig.set_ptt(self.hamlib_ptt_type, 1) + + state_before_transmit = static.CHANNEL_STATE + static.CHANNEL_STATE = 'SENDING_DATA' + + self.c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) + freedv = self.c_lib.freedv_open(static.ARQ_DATA_CHANNEL_MODE) + + static.FREEDV_DATA_BYTES_PER_FRAME = int(self.c_lib.freedv_get_bits_per_modem_frame(freedv) / 8) + static.FREEDV_DATA_PAYLOAD_PER_FRAME = static.FREEDV_DATA_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 + n_tx_preamble_modem_samples = self.c_lib.freedv_get_n_tx_preamble_modem_samples(freedv) + n_tx_postamble_modem_samples = self.c_lib.freedv_get_n_tx_postamble_modem_samples(freedv) + + mod_out = ctypes.c_short * n_tx_modem_samples + mod_out = mod_out() + + mod_out_preamble = ctypes.c_short * n_tx_preamble_modem_samples # *2 #1760 for mode 10,11,12 #4000 for mode 9 + mod_out_preamble = mod_out_preamble() + + mod_out_postamble = ctypes.c_short * n_tx_postamble_modem_samples # *2 #1760 for mode 10,11,12 #4000 for mode 9 + mod_out_postamble = mod_out_postamble() + + + self.streambuffer = bytearray() + + self.c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) + self.streambuffer += bytes(mod_out_preamble) + + if not static.ARQ_RPT_RECEIVED: + + for n in range(0, static.ARQ_TX_N_FRAMES_PER_BURST): + # ---------------------------BUILD ARQ BURST --------------------------------------------------------------------- + frame_type = 10 + n + 1 # static.ARQ_TX_N_FRAMES_PER_BURST + frame_type = bytes([frame_type]) + + payload_data = bytes(static.TX_BUFFER[static.ARQ_N_SENT_FRAMES + n]) + + n_current_arq_frame = static.ARQ_N_SENT_FRAMES + n + 1 + static.ARQ_TX_N_CURRENT_ARQ_FRAME = n_current_arq_frame.to_bytes(2, byteorder='big') + + n_total_arq_frame = len(static.TX_BUFFER) + static.ARQ_TX_N_TOTAL_ARQ_FRAMES = n_total_arq_frame.to_bytes(2, byteorder='big') + + arqframe = frame_type + \ + bytes([static.ARQ_TX_N_FRAMES_PER_BURST]) + \ + static.ARQ_TX_N_CURRENT_ARQ_FRAME + \ + static.ARQ_TX_N_TOTAL_ARQ_FRAMES + \ + static.DXCALLSIGN_CRC8 + \ + static.MYCALLSIGN_CRC8 + \ + payload_data + + buffer = bytearray(static.FREEDV_DATA_PAYLOAD_PER_FRAME) # create TX buffer + buffer[:len(arqframe)] = arqframe # set buffersize to length of data which will be send + + crc = ctypes.c_ushort(self.c_lib.freedv_gen_crc16(bytes(buffer), static.FREEDV_DATA_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 = (ctypes.c_ubyte * static.FREEDV_DATA_BYTES_PER_FRAME).from_buffer_copy(buffer) + + self.c_lib.freedv_rawdatatx(freedv, mod_out, data) # modulate DATA and safe it into mod_out pointer + self.streambuffer += bytes(mod_out) + + + elif static.ARQ_RPT_RECEIVED: + + for n in range(0, len(static.ARQ_RPT_FRAMES)): + + missing_frame = int.from_bytes(static.ARQ_RPT_FRAMES[n], "big") + + # ---------------------------BUILD ARQ BURST --------------------------------------------------------------------- + frame_type = 10 + missing_frame # static.ARQ_TX_N_FRAMES_PER_BURST + + frame_type = bytes([frame_type]) + + try: + payload_data = bytes(static.TX_BUFFER[static.ARQ_N_SENT_FRAMES + missing_frame - 1]) + except: + print("modem buffer selection problem with ARQ RPT frames") + + n_current_arq_frame = static.ARQ_N_SENT_FRAMES + missing_frame + static.ARQ_TX_N_CURRENT_ARQ_FRAME = n_current_arq_frame.to_bytes(2, byteorder='big') + + n_total_arq_frame = len(static.TX_BUFFER) + static.ARQ_TX_N_TOTAL_ARQ_FRAMES = n_total_arq_frame.to_bytes(2, byteorder='big') + + arqframe = frame_type + \ + bytes([static.ARQ_TX_N_FRAMES_PER_BURST]) + \ + static.ARQ_TX_N_CURRENT_ARQ_FRAME + \ + static.ARQ_TX_N_TOTAL_ARQ_FRAMES + \ + static.DXCALLSIGN_CRC8 + \ + static.MYCALLSIGN_CRC8 + \ + payload_data + + buffer = bytearray(static.FREEDV_DATA_PAYLOAD_PER_FRAME) # create TX buffer + buffer[:len(arqframe)] = arqframe # set buffersize to length of data which will be send + + crc = ctypes.c_ushort(self.c_lib.freedv_gen_crc16(bytes(buffer), static.FREEDV_DATA_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 = (ctypes.c_ubyte * static.FREEDV_DATA_BYTES_PER_FRAME).from_buffer_copy(buffer) + + self.c_lib.freedv_rawdatatx(freedv, mod_out, data) # modulate DATA and safe it into mod_out pointer + self.streambuffer += bytes(mod_out) + + + self.c_lib.freedv_rawdatapostambletx(freedv, mod_out_postamble) + self.streambuffer += bytes(mod_out_postamble) + + converted_audio = audioop.ratecv(self.streambuffer,2,1,static.MODEM_SAMPLE_RATE, static.AUDIO_SAMPLE_RATE_TX, None) + self.streambuffer = bytes(converted_audio[0]) + + # -------------- transmit audio + + self.ptt_and_wait(True) + self.audio_writing_to_stream = True + + + # wait until audio has been processed + while self.audio_writing_to_stream: + time.sleep(0.01) + static.CHANNEL_STATE = 'SENDING_DATA' + + + static.CHANNEL_STATE = 'RECEIVING_SIGNALLING' + + self.ptt_and_wait(False) + + self.c_lib.freedv_close(freedv) +# -------------------------------------------------------------------------------------------------------- + + def receive(self, mode): + force = 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) + + if mode == static.FREEDV_SIGNALLING_MODE: + static.FREEDV_SIGNALLING_BYTES_PER_FRAME = bytes_per_frame + static.FREEDV_SIGNALLING_PAYLOAD_PER_FRAME = bytes_per_frame - 2 + + self.c_lib.freedv_set_frames_per_burst(freedv, 1) + + elif mode == static.ARQ_DATA_CHANNEL_MODE: + static.FREEDV_DATA_BYTES_PER_FRAME = bytes_per_frame + static.FREEDV_DATA_PAYLOAD_PER_FRAME = bytes_per_frame - 2 + + self.c_lib.freedv_set_frames_per_burst(freedv, 0) + else: + #pass + self.c_lib.freedv_set_frames_per_burst(freedv, 0) + + bytes_out = (ctypes.c_ubyte * bytes_per_frame) + bytes_out = bytes_out() # get pointer to bytes_out + + while static.FREEDV_RECEIVE == True: + time.sleep(0.01) + + # lets get the frequency, mode and bandwith + self.get_radio_stats() + + # demod loop + while (static.CHANNEL_STATE == 'RECEIVING_DATA' and static.ARQ_DATA_CHANNEL_MODE == mode) or (static.CHANNEL_STATE == 'RECEIVING_SIGNALLING' and static.FREEDV_SIGNALLING_MODE == mode): + time.sleep(0.01) + + # refresh vars, so the correct parameters of the used mode are set + if mode == static.ARQ_DATA_CHANNEL_MODE: + static.FREEDV_DATA_BYTES_PER_FRAME = bytes_per_frame + static.FREEDV_DATA_PAYLOAD_PER_FRAME = bytes_per_frame - 2 + + nin = self.c_lib.freedv_nin(freedv) + nin = int(nin*(static.AUDIO_SAMPLE_RATE_RX/static.MODEM_SAMPLE_RATE)) + + data_in = self.stream_rx.read(nin, exception_on_overflow=False) + + self.calculate_fft(data_in) + + + data_in = audioop.ratecv(data_in,2,1,static.AUDIO_SAMPLE_RATE_RX, static.MODEM_SAMPLE_RATE, None) + data_in = data_in[0] + + static.AUDIO_RMS = audioop.rms(data_in, 2) + nbytes = self.c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio + #print("listening-" + str(mode) + " - " + "nin: " + str(nin) + " - " + str(self.c_lib.freedv_get_rx_status(freedv))) + + self.calculate_snr(freedv) + # forward data only if broadcast or we are the receiver + # bytes_out[1:2] == callsign check for signalling frames, bytes_out[6:7] == callsign check for data frames, bytes_out[1:2] == b'\x01' --> broadcasts like CQ + # we could also create an own function, which returns True. In this case we could add callsign blacklists and so on + if nbytes == bytes_per_frame and bytes(bytes_out[1:2]) == static.MYCALLSIGN_CRC8 or bytes(bytes_out[6:7]) == static.MYCALLSIGN_CRC8 or bytes(bytes_out[1:2]) == b'\x01': + + self.calculate_snr(freedv) + + + # CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------ + frametype = int.from_bytes(bytes(bytes_out[:1]), "big") + frame = frametype - 10 + n_frames_per_burst = int.from_bytes(bytes(bytes_out[1:2]), "big") + + #self.c_lib.freedv_set_frames_per_burst(freedv_data, n_frames_per_burst); + + if 50 >= frametype >= 10: + if frame != 3 or force == True: + + data_handler.arq_data_received(bytes(bytes_out[:-2])) # send payload data to arq checker without CRC16 + + #print("static.ARQ_RX_BURST_BUFFER.count(None) " + str(static.ARQ_RX_BURST_BUFFER.count(None))) + if static.ARQ_RX_BURST_BUFFER.count(None) <= 1: + logging.debug("FULL BURST BUFFER ---> UNSYNC") + self.c_lib.freedv_set_sync(freedv, 0) + + else: + logging.critical("---------------------------SIMULATED MISSING FRAME") + force = True + + # BURST ACK + elif frametype == 60: + logging.debug("ACK RECEIVED....") + data_handler.burst_ack_received() + + # FRAME ACK + elif frametype == 61: + logging.debug("FRAME ACK RECEIVED....") + data_handler.frame_ack_received() + + # FRAME RPT + elif frametype == 62: + logging.debug("REPEAT REQUEST RECEIVED....") + data_handler.burst_rpt_received(bytes_out[:-2]) + + # FRAME NAK + elif frametype == 63: + logging.debug("FRAME NAK RECEIVED....") + data_handler.frame_nack_received(bytes_out[:-2]) + + # CQ FRAME + elif frametype == 200: + logging.debug("CQ RECEIVED....") + data_handler.received_cq(bytes_out[:-2]) + + # PING FRAME + elif frametype == 210: + logging.debug("PING RECEIVED....") + data_handler.received_ping(bytes_out[:-2]) + + # PING ACK + elif frametype == 211: + logging.debug("PING ACK RECEIVED....") + data_handler.received_ping_ack(bytes_out[:-2]) + + # ARQ CONNECT + elif frametype == 220: + logging.info("ARQ CONNECT RECEIVED....") + data_handler.arq_received_connect(bytes_out[:-2]) + + # ARQ CONNECT ACK / KEEP ALIVE + elif frametype == 221: + logging.info("ARQ CONNECT ACK RECEIVED / KEEP ALIVE....") + data_handler.arq_received_connect_keep_alive(bytes_out[:-2]) + + # ARQ CONNECT ACK / KEEP ALIVE + elif frametype == 222: + logging.debug("ARQ DISCONNECT RECEIVED") + data_handler.arq_disconnect_received(bytes_out[:-2]) + + # ARQ FILE TRANSFER RECEIVED! + elif frametype == 225: + logging.debug("ARQ arq_received_data_channel_opener RECEIVED") + data_handler.arq_received_data_channel_opener(bytes_out[:-2]) + + # ARQ CHANNEL IS OPENED + elif frametype == 226: + logging.debug("ARQ arq_received_channel_is_open RECEIVED") + data_handler.arq_received_channel_is_open(bytes_out[:-2]) + + # ARQ CONNECT ACK / KEEP ALIVE + elif frametype == 230: + logging.debug("BEACON RECEIVED") + data_handler.received_beacon(bytes_out[:-2]) + + else: + logging.info("OTHER FRAME: " + str(bytes_out[:-2])) + print(frametype) + + # DO UNSYNC AFTER LAST BURST by checking the frame nums agains the total frames per burst + if frame == n_frames_per_burst: + logging.debug("LAST FRAME ---> UNSYNC") + + bytes_out = (ctypes.c_ubyte * bytes_per_frame) + bytes_out = bytes_out() # get pointer to bytes_out + + self.c_lib.freedv_set_sync(freedv, 0) # FORCE UNSYNC + + # clear bytes_out buffer to be ready for next frames after successfull decoding + + bytes_out = (ctypes.c_ubyte * bytes_per_frame) + bytes_out = bytes_out() # get pointer to bytes_out + + else: + # for debugging purposes to receive all data + pass + # print(bytes_out[:-2]) + + def calculate_ber(self, freedv): + Tbits = self.c_lib.freedv_get_total_bits(freedv) + Terrs = self.c_lib.freedv_get_total_bit_errors(freedv) + + if Tbits != 0: + ber = (Terrs / Tbits) * 100 + static.BER = int(ber) + + self.c_lib.freedv_set_total_bit_errors(freedv, 0) + self.c_lib.freedv_set_total_bits(freedv, 0) + + def calculate_snr(self, freedv): + + modem_stats_snr = c_float() + modem_stats_sync = c_int() + + self.c_lib.freedv_get_modem_stats(freedv,byref(modem_stats_sync), byref(modem_stats_snr)) + modem_stats_snr = modem_stats_snr.value + try: + static.SNR = round(modem_stats_snr,1) + except: + static.SNR = 0 + + def get_radio_stats(self): + static.HAMLIB_FREQUENCY = int(self.my_rig.get_freq()) + (hamlib_mode, static.HAMLIB_BANDWITH) = self.my_rig.get_mode() + static.HAMLIB_MODE = Hamlib.rig_strrmode(hamlib_mode) + + + def calculate_fft(self, data_in): + data_in_array = np.frombuffer(data_in, dtype=np.int16) + #print(fft_raw) + #fft_raw = fft(data_in_array) + #print(fft_raw) + #fft_raw = data_in.hex() + #print(fft_raw) + #static.FFT = fft_raw.tolist() + #fft_raw = fft_raw.tobytes() + + rate = 48000 + M = 1024 + freqs, times, Sx = signal.spectrogram(data_in_array, fs=rate, window='hanning', nperseg=1024, noverlap=M - 100, detrend=False, scaling='spectrum', return_onesided=True) + + freqs, times, Sx = signal.spectrogram(data_in_array, fs=rate, return_onesided=True, axis=-1) + + + + + #print(Sx) + + #fft_raw = Sx.tobytes() + #print(fft_raw) + #static.FFT = fft_raw.hex() + #static.FFT = fft_raw + data_in = np.frombuffer(data_in, dtype=np.int16) + data = fft(data_in) + #print(data) + #data = getFFT(data_in, 48000, 2048) + #print(data) + + #data = abs(data) * 2 / np.sum(8192) + + #data = abs(data) // np.sum(1024) + + + #data = np.frombuffer(data_in, dtype=np.int16) + data.resize((1,2048)) + #data = np.delete(data,0) + + + #data = data.tobytes() + #print(data) + static.FFT = data.tolist() + #static.FFT = data.hex() + +def getFFT(data, rate, chunk_size, log_scale=False): + data = data * np.hamming(len(data)) + try: + FFT = np.abs(np.fft.rfft(data)[1:]) + except: + FFT = np.fft.fft(data) + left, right = np.split(np.abs(FFT), 2) + FFT = np.add(left, right[::-1]) + + #fftx = np.fft.fftfreq(chunk_size, d=1.0/rate) + #fftx = np.split(np.abs(fftx), 2)[0] + + if log_scale: + try: + FFT = np.multiply(20, np.log10(FFT)) + except Exception as e: + print('Log(FFT) failed: %s' %str(e)) + + return FFT + + + diff --git a/sock.py b/sock.py new file mode 100644 index 00000000..f9fe7c46 --- /dev/null +++ b/sock.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 25 21:25:14 2020 + +@author: DJ2LS +""" + +import socketserver +import threading +import logging +import json +import asyncio +import time + +import static +import data_handler +import helpers + + +class CMDTCPRequestHandler(socketserver.BaseRequestHandler): + + def handle(self): + print("Client connected...") + + # loop through socket buffer until timeout is reached. then close buffer + socketTimeout = time.time() + 3 + while socketTimeout > time.time(): + + time.sleep(0.01) + encoding = 'utf-8' + #data = str(self.request.recv(1024), 'utf-8') + + data = bytes() + + # we need to loop through buffer until end of chunk is reached or timeout occured + while True and socketTimeout > time.time(): + chunk = self.request.recv(1024) # .strip() + data += chunk + if chunk.endswith(b'\n'): + break + data = data[:-1] # remove b'\n' + data = str(data, 'utf-8') + + if len(data) > 0: + socketTimeout = time.time() + static.SOCKET_TIMEOUT + + # convert data to json object + # we need to do some error handling in case of socket timeout + + try: + received_json = json.loads(data) + #print(received_json) + except: + received_json = '' + + + # GET COMMANDS + # "command" : "..." + + # SET COMMANDS + # "command" : "..." + # "parameter" : " ..." + + # DATA COMMANDS + # "command" : "..." + # "type" : "..." + # "dxcallsign" : "..." + # "data" : "..." + + + try: + # SOCKETTEST --------------------------------------------------- + #if data == 'SOCKETTEST': + if received_json["command"] == "SOCKETTEST": + #cur_thread = threading.current_thread() + response = bytes("WELL DONE! YOU ARE ABLE TO COMMUNICATE WITH THE TNC", encoding) + self.request.sendall(response) + + # CQ CQ CQ ----------------------------------------------------- + #if data == 'CQCQCQ': + if received_json["command"] == "CQCQCQ": + asyncio.run(data_handler.transmit_cq()) + + + # PING ---------------------------------------------------------- + #if data.startswith('PING:'): + if received_json["command"] == "PING": + # send ping frame and wait for ACK + dxcallsign = received_json["dxcallsign"] + asyncio.run(data_handler.transmit_ping(dxcallsign)) + + # ARQ CONNECT TO CALLSIGN ---------------------------------------- + #if data.startswith('ARQ:CONNECT:'): + #if received_json["command"] == "ARQ:CONNECT": + # + # dxcallsign = received_json["dxcallsign"] + # static.DXCALLSIGN = bytes(dxcallsign, 'utf-8') + # static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN) + + # if static.ARQ_STATE == 'CONNECTED': + # # here we could disconnect + # pass + + # if static.TNC_STATE == 'IDLE': + + # asyncio.run(data_handler.arq_connect()) + + # ARQ DISCONNECT FROM CALLSIGN ---------------------------------------- + #if received_json["command"] == "ARQ:DISCONNECT": + # asyncio.run(data_handler.arq_disconnect()) + + + if received_json["type"] == 'ARQ' and received_json["command"] == "OPEN_DATA_CHANNEL": # and static.ARQ_STATE == 'CONNECTED': + static.ARQ_READY_FOR_DATA = False + static.TNC_STATE = 'BUSY' + + dxcallsign = received_json["dxcallsign"] + static.DXCALLSIGN = bytes(dxcallsign, 'utf-8') + static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN) + + asyncio.run(data_handler.arq_open_data_channel()) + + + if received_json["type"] == 'ARQ' and received_json["command"] == "DATA":# and static.ARQ_READY_FOR_DATA == True: # and static.ARQ_STATE == 'CONNECTED' : + static.TNC_STATE = 'BUSY' + + #on a new transmission we reset the timer + static.ARQ_START_OF_TRANSMISSION = int(time.time()) + + + dxcallsign = received_json["dxcallsign"] + static.DXCALLSIGN = bytes(dxcallsign, 'utf-8') + static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN) + + + data_out = bytes(received_json["data"], 'utf-8') + + mode = int(received_json["mode"]) + + n_frames = int(received_json["n_frames"]) + + #ARQ_DATA_THREAD = threading.Thread(target=data_handler.arq_transmit, args=[data_out], name="ARQ_DATA") + #ARQ_DATA_THREAD.start() + + ARQ_DATA_THREAD = threading.Thread(target=data_handler.open_dc_and_transmit, args=[data_out, mode, n_frames], name="ARQ_DATA") + ARQ_DATA_THREAD.start() + # asyncio.run(data_handler.arq_transmit(data_out)) + + # SETTINGS AND STATUS --------------------------------------------- + if received_json["type"] == 'SET' and received_json["command"] == 'MYCALLSIGN': + callsign = received_json["parameter"] + + if bytes(callsign, encoding) == b'': + self.request.sendall(b'INVALID CALLSIGN') + else: + static.MYCALLSIGN = bytes(callsign, encoding) + static.MYCALLSIGN_CRC8 = helpers.get_crc_8(static.MYCALLSIGN) + logging.info("CMD | MYCALLSIGN: " + str(static.MYCALLSIGN)) + + if received_json["type"] == 'SET' and received_json["command"] == 'MYGRID': + mygrid = received_json["parameter"] + + if bytes(mygrid, encoding) == b'': + self.request.sendall(b'INVALID GRID') + else: + static.MYGRID = bytes(mygrid, encoding) + logging.info("CMD | MYGRID: " + str(static.MYGRID)) + + + if received_json["type"] == 'GET' and received_json["command"] == 'STATION_INFO': + output = { + "COMMAND": "STATION_INFO", + "MY_CALLSIGN": str(static.MYCALLSIGN, encoding), + "DX_CALLSIGN": str(static.DXCALLSIGN, encoding), + "DX_GRID": str(static.DXGRID, encoding) + } + + jsondata = json.dumps(output) + self.request.sendall(bytes(jsondata, encoding)) + + if received_json["type"] == 'GET' and received_json["command"] == 'TNC_STATE': + output = { + "COMMAND": "TNC_STATE", + "PTT_STATE": str(static.PTT_STATE), + "CHANNEL_STATE": str(static.CHANNEL_STATE), + "TNC_STATE": str(static.TNC_STATE), + "ARQ_STATE": str(static.ARQ_STATE), + "AUDIO_RMS": str(static.AUDIO_RMS), + "BER": str(static.BER), + "SNR": str(static.SNR), + "FREQUENCY" : str(static.HAMLIB_FREQUENCY), + "MODE" : str(static.HAMLIB_MODE), + "BANDWITH" : str(static.HAMLIB_BANDWITH), + "FFT" : str(static.FFT) + } + + jsondata = json.dumps(output) + print(len(jsondata)) + self.request.sendall(bytes(jsondata, encoding)) + + if received_json["type"] == 'GET' and received_json["command"] == 'FFT': + output = { + "FFT" : str(static.FFT) + } + + jsondata = json.dumps(output) + self.request.sendall(bytes(jsondata, encoding)) + + if received_json["type"] == 'GET' and received_json["command"] == 'DATA_STATE': + output = { + "COMMAND": "DATA_STATE", + "RX_BUFFER_LENGTH": str(len(static.RX_BUFFER)), + "TX_N_MAX_RETRIES": str(static.TX_N_MAX_RETRIES), + "ARQ_TX_N_FRAMES_PER_BURST": str(static.ARQ_TX_N_FRAMES_PER_BURST), + "ARQ_TX_N_BURSTS": str(static.ARQ_TX_N_BURSTS), + "ARQ_TX_N_CURRENT_ARQ_FRAME": str(int.from_bytes(bytes(static.ARQ_TX_N_CURRENT_ARQ_FRAME), "big")), + "ARQ_TX_N_TOTAL_ARQ_FRAMES": str(int.from_bytes(bytes(static.ARQ_TX_N_TOTAL_ARQ_FRAMES), "big")), + "ARQ_RX_FRAME_N_BURSTS": str(static.ARQ_RX_FRAME_N_BURSTS), + "ARQ_RX_N_CURRENT_ARQ_FRAME": str(static.ARQ_RX_N_CURRENT_ARQ_FRAME), + "ARQ_N_ARQ_FRAMES_PER_DATA_FRAME": str(static.ARQ_N_ARQ_FRAMES_PER_DATA_FRAME) + } + + jsondata = json.dumps(output) + self.request.sendall(bytes(jsondata, encoding)) + + if received_json["type"] == 'GET' and received_json["command"] == 'HEARD_STATIONS': + output = [] + for i in range(0, len(static.HEARD_STATIONS)): + output.append({"COMMAND": "HEARD_STATIONS", "CALLSIGN": str(static.HEARD_STATIONS[i][0], 'utf-8'),"DXGRID": str(static.HEARD_STATIONS[i][1], 'utf-8'), "TIMESTAMP": static.HEARD_STATIONS[i][2], "DATATYPE": static.HEARD_STATIONS[i][3]}) + + jsondata = json.dumps(output) + self.request.sendall(bytes(jsondata, encoding)) + + + if received_json["type"] == 'GET' and received_json["command"] == '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])) + + if received_json["type"] == 'SET' and received_json["command"] == 'DEL_RX_BUFFER': + static.RX_BUFFER = [] + + #exception, if JSON cant be decoded + except: + print("Wrong command") + print("Client disconnected...") + +def start_cmd_socket(): + + try: + logging.info("SRV | STARTING TCP/IP SOCKET FOR CMD ON PORT: " + str(static.PORT)) + socketserver.TCPServer.allow_reuse_address = True # https://stackoverflow.com/a/16641793 + cmdserver = socketserver.TCPServer((static.HOST, static.PORT), CMDTCPRequestHandler) + cmdserver.serve_forever() + + finally: + cmdserver.server_close() diff --git a/static.py b/static.py new file mode 100644 index 00000000..d72f94d7 --- /dev/null +++ b/static.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 11:13:57 2020 + +@author: DJ2LS +""" + +# DAEMON +DAEMONPORT = 3001 +TNCSTARTED = False +TNCPROCESS = 0 + + + + + +# Operator Defaults +MYCALLSIGN = b'AA0AA' +MYCALLSIGN_CRC8 = b'A' + +DXCALLSIGN = b'AA0AA' +DXCALLSIGN_CRC8 = b'A' + +MYGRID = b'' +DXGRID = b'' + +# --------------------------------- + +# Server Defaults +HOST = "0.0.0.0" +PORT = 3000 +SOCKET_TIMEOUT = 3 # seconds +# --------------------------------- + +# HAMLIB DEFAULTS + +# RIG_PTT_NONE +# No PTT available + +# RIG_PTT_RIG +# Legacy PTT + +# RIG_PTT_SERIAL_DTR +# PTT control through serial DTR signal + +# RIG_PTT_SERIAL_RTS +# PTT control through serial RTS signal + +# RIG_PTT_PARALLEL +# PTT control through parallel port + +# RIG_PTT_RIG_MICDATA +# Legacy PTT, supports RIG_PTT_ON_MIC/RIG_PTT_ON_DATA + +# RIG_PTT_CM108 +# PTT control through CM108 GPIO pin + +HAMLIB_PTT_TYPE = 'RTS' +PTT_STATE = False + +HAMLIB_DEVICE_ID = 0 +HAMLIB_DEVICE_PORT = '/dev/ttyUSB0' + +HAMLIB_FREQUENCY = 0 +HAMLIB_MODE = '' +HAMLIB_BANDWITH = 0 +# ------------------------- +# FreeDV Defaults +FREEDV_RECEIVE = True + +FREEDV_SIGNALLING_MODE = 14 + +FREEDV_DATA_BYTES_PER_FRAME = 0 +FREEDV_DATA_PAYLOAD_PER_FRAME = 0 +FREEDV_SIGNALLING_BYTES_PER_FRAME = 0 +FREEDV_SIGNALLING_PAYLOAD_PER_FRAME = 0 + +BER = 0 +SNR = 0 +# --------------------------------- + +# Audio Defaults +AUDIO_INPUT_DEVICE = 1 +AUDIO_OUTPUT_DEVICE = 1 +#TX_SAMPLE_STATE = None +#RX_SAMPLE_STATE = None + +AUDIO_SAMPLE_RATE_RX = 48000 +AUDIO_SAMPLE_RATE_TX = 48000 +MODEM_SAMPLE_RATE = 8000 # 8000 +AUDIO_FRAMES_PER_BUFFER = 8192 # 256 # 512 # 1024 #2048 --> nicht 880 # 128 gut, 256, 1024 16384 +AUDIO_CHANNELS = 1 +AUDIO_RMS = 0 +FFT = [] +# --------------------------------- + +# ARQ DEFAULTS +TX_N_MAX_RETRIES = 3 +TX_N_RETRIES = 0 + +ARQ_TX_N_FRAMES_PER_BURST = 0 +ARQ_TX_N_BURSTS = 0 + +ARQ_PAYLOAD_PER_FRAME = 0 + +ARQ_RX_BURST_BUFFER = [] +ARQ_RX_FRAME_BUFFER = [] +ARQ_RX_FRAME_N_BURSTS = 0 + +# TX +ARQ_TX_N_CURRENT_ARQ_FRAME = 0 +ARQ_TX_N_TOTAL_ARQ_FRAMES = 0 +## + +# RX +ARQ_N_ARQ_FRAMES_PER_DATA_FRAME = 0 # total number of arq frames per data frame +ARQ_RX_N_CURRENT_ARQ_FRAME = 0 +## + +ARQ_N_RX_ARQ_FRAMES = 0 # total number of received frames +ARQ_N_RX_FRAMES_PER_BURSTS = 0 # NUMBER OF FRAMES WE ARE WAITING FOR --> GOT DATA FROM RECEIVED FRAME +ARQ_ACK_PAYLOAD_PER_FRAME = 0 # PAYLOAD per ACK frame + +ARQ_ACK_RECEIVED = False # set to 1 if ACK received +ARQ_RX_ACK_TIMEOUT = False # set to 1 if timeut reached +ARQ_RX_ACK_TIMEOUT_SECONDS = 7.0 # timeout for waiting for ACK frames + +ARQ_FRAME_ACK_RECEIVED = False # set to 1 if FRAME ACK received +ARQ_RX_FRAME_TIMEOUT = False +ARQ_RX_FRAME_TIMEOUT_SECONDS = 10.0 + + +ARQ_RX_RPT_TIMEOUT = False +ARQ_RX_RPT_TIMEOUT_SECONDS = 10.0 +ARQ_RPT_RECEIVED = False # indicate if RPT frame has been received +ARQ_RPT_FRAMES = [] # buffer for frames which are requested to repeat + +FRAME_CRC = b'' +FRAME_BOF = b'\xAA\xAA' # here we define 2 bytes for the BOF +FRAME_EOF = b'\xFF\xFF' # here we define 2 bytes for the EOF +ARQ_FRAME_BOF_RECEIVED = False # status, if we received a BOF of a data frame +ARQ_FRAME_EOF_RECEIVED = False # status, if we received a EOF of a data frame + +ARQ_N_SENT_FRAMES = 0 # counter for already sent frames + + +# ARQ STATES: +# IDLE +# RECEIVING_DATA +# SENDING_DATA +# RECEIVING_SIGNALLING +# SENDING_ACK +# ACK_RECEIVED + +# DATA +ARQ_STATE = 'IDLE' + +# RECEIVING_SIGNALLING +# RECEIVING_DATA_10 +# RECEIVING_DATA_11 +# RECEIVING_DATA_12 +CHANNEL_STATE = 'RECEIVING_SIGNALLING' + +# IDLE +# BUSY +TNC_STATE = 'IDLE' + +# MODE FOR SENDING AN RECEIVING DATA DURING ARQ SESSION +ARQ_OPEN_DATA_CHANNEL_RETRIES = 3 +ARQ_READY_FOR_DATA = False +ARQ_DATA_CHANNEL_MODE = 0 +ARQ_DATA_CHANNEL_LAST_RECEIVED = 0 + +# BIT RATE MESSUREMENT +ARQ_START_OF_TRANSMISSION = 0 +ARQ_START_OF_BURST = 0 +#ARQ_END_OF_TRANSMISSION = 0 +ARQ_BITS_PER_SECOND = 0 +ARQ_BYTES_PER_MINUTE = 0 + +# ------- TX BUFFER +TX_BUFFER_SIZE = 0 +TX_BUFFER = [] +# ------- RX BUFFER +RX_BUFFER = [] +RX_BUFFER_SIZE = 0 + +# ------- HEARD STATIOS BUFFER +HEARD_STATIONS = [] diff --git a/tools/cleanup.sh b/tools/cleanup.sh new file mode 100644 index 00000000..3343de02 --- /dev/null +++ b/tools/cleanup.sh @@ -0,0 +1,3 @@ +#autopep8 --in-place --select W291,W293,W391,E231 --ignore E501 ../modem.py ../data_handler.py ../main.py ../sock.py ../static.py ../helpers.py +autopep8 --in-place --ignore E501 ../modem.py ../data_handler.py ../main.py ../sock.py ../static.py ../helpers.py + diff --git a/tools/init_and_send.sh b/tools/init_and_send.sh new file mode 100644 index 00000000..75d084ac --- /dev/null +++ b/tools/init_and_send.sh @@ -0,0 +1,23 @@ +for i in {1..5} +do + echo "Welcome $i times" +done + +python3 readfromsocket.py --port 3002 --data "SET:MYCALLSIGN:DJ2LS" +python3 readfromsocket.py --port 3000 --data "SET:MYCALLSIGN:DH3WO" +sleep 3 +#python3 readfromsocket.py --port 3002 --data "CQCQCQ" +#sleep 5 +python3 readfromsocket.py --port 3002 --data "PING:DH3WO" +sleep 5 +python3 readfromsocket.py --port 3002 --data "ARQ:CONNECT:DH3WO" +#sleep 1 +python3 readfromsocket.py --port 3000 --data "GET:ARQ_STATE" +sleep 5 +echo "ACHTUNG DATEI" +python3 socketclient.py --port 3002 --random 100 + + +sleep 10 +python3 readfromsocket.py --port 3000 --data "ARQ:DISCONNECT" +echo "ende..." diff --git a/tools/install_jate.sh b/tools/install_jate.sh new file mode 100644 index 00000000..c8c590ab --- /dev/null +++ b/tools/install_jate.sh @@ -0,0 +1,30 @@ +#/bin/bash + +cd ~ +rm -rf FreeDV-JATE +git clone --branch dev https://github.com/DJ2LS/FreeDV-JATE.git + + +cd ~/FreeDV-JATE +rm -rf codec2 +git clone https://github.com/drowe67/codec2.git +cd codec2 && mkdir build_linux && cd build_linux +cmake ../ +make + + +cd ~/FreeDV-JATE +rm -rf LPCNet +git clone https://github.com/drowe67/LPCNet +cd LPCNet && mkdir build_linux && cd build_linux +cmake -DCODEC2_BUILD_DIR=~/FreeDV-JATE/codec2/build_linux ../ +make + + +cd ~/FreeDV-JATE/codec2/build_linux && rm -Rf * +cmake -DLPCNET_BUILD_DIR=~/FreeDV-JATE/LPCNet/build_linux .. +make + + +cd ~/FreeDV-JATE/tools/ +git clone https://github.com/DJ2LS/FreeDV-JATE-GUI.git diff --git a/tools/list_audio_devices.py b/tools/list_audio_devices.py new file mode 100644 index 00000000..5c0db651 --- /dev/null +++ b/tools/list_audio_devices.py @@ -0,0 +1,24 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import pyaudio + + +def list_audio_devices(): + p = pyaudio.PyAudio() + devices = [] + print("--------------------------------------------------------------------") + for x in range(0, p.get_device_count()): + devices.append(f"{x} - {p.get_device_info_by_index(x)['name']}") + + for line in devices: + print(line) + + + + + + + + +list_audio_devices() diff --git a/tools/readfromsocket.py b/tools/readfromsocket.py new file mode 100644 index 00000000..9b63a427 --- /dev/null +++ b/tools/readfromsocket.py @@ -0,0 +1,37 @@ +#!/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 + +print(len(b'\n')) + +while True: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + + sock.connect((ip, port)) + sock.sendall(bytes(message, 'utf-8') + b'\n') + response = str(sock.recv(1024), 'utf-8') + print("CMD: {}".format(response)) + False + break diff --git a/tools/socketclient.py b/tools/socketclient.py new file mode 100644 index 00000000..753b4bea --- /dev/null +++ b/tools/socketclient.py @@ -0,0 +1,68 @@ +#!/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 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('--random', dest="datalength", default=False, help="data", type=int) + + + + +args = parser.parse_args() + + +data = create_string(args.datalength) +data = bytes("ARQ:DATA:" + "" + data + "" + "\n", "utf-8") + + + +#print(data) + + +HOST, PORT = "localhost", args.socket_port +#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(data) + # Receive data from the server and shut down + received = str(sock.recv(1024), "utf-8") + +print("Sent: {}".format(data)) + + +