diff --git a/__main__.py b/__main__.py new file mode 100644 index 00000000..573de2e2 --- /dev/null +++ b/__main__.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 16:54:35 2020 + +@author: DJ2LS +""" + +import sys +import logging +import argparse +import socketserver + +import freedv +import sound +import tnc +import commands + + + + + + +def main(): + + parser = argparse.ArgumentParser(description='Simons TEST TNC') + parser.add_argument('--rx-sound-device', dest="audio_input_device", default=False, help="The sound card used to rx.", type=int) + parser.add_argument('--tx-sound-device', dest="audio_output_device", default=False, help="The sound card used to tx.", type=int) + parser.add_argument('--list-sound-device', dest="list_sound_devices", action='store_true', help="List audio devices") + parser.add_argument('--port', dest="socket_port", default=9000, help="Set the port, the socket is listening on.", type=int) + + args = parser.parse_args() + + if args.list_sound_devices: + for line in commands.Helpers.getAudioDevices(): + print(line) + sys.exit(0) + + + logger = logging.getLogger() + logger.setLevel("INFO") #DEBUG>INFO>WARNING>ERROR>CRITICAL + logger.info("SIMONS ERSTES TNC PROGRAMM....DURCHHALTEN!") + +#LADE FreeDV Klasse sonst Fehler + try: + modem = freedv.FreeDV() + except OSError: + logger.error("FREEDV NICHT GEFUNDEN") + sys.exit(0) + +#LADE Audio Klasse sonst Fehler + try: + audio = sound.Audio( + defaultFrames = 1024 * 8, + audio_input_device=args.audio_input_device, + audio_output_device=args.audio_output_device, + tx_sample_state = None, + audio_sample_rate=48000, + modem_sample_rate=8000, + frames_per_buffer=1024, + audio_channels=1, + ) + audio.Record() + + except OSError: + logger.error("AUDIO NICHT GEFUNDEN") + sys.exit(0) + +#LADE TNC Klasse sonst Fehler + try: + HOST, PORT = "localhost", args.socket_port + try: + logger.info("Starting SOCKET SERVER") + server = socketserver.TCPServer((HOST, PORT), tnc.MyTCPHandler) + server.serve_forever() + finally: + server.server_close() + + except OSError: + logger.error("TNC NICHT GEFUNDEN") + sys.exit(0) + + ####################### TEST BEREICH ############################ + + + + + + ################################################################# + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/commands.py b/commands.py new file mode 100644 index 00000000..7dbc6322 --- /dev/null +++ b/commands.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 23:10:21 2020 + +@author: DJ2LS +""" + +import freedv +import logging +import sound + +import pyaudio + +class Helpers(): + + def crc16(data: bytes): + + #https://stackoverflow.com/a/60604183 + + + xor_in = 0x0000 # initial value + xor_out = 0x0000 # final XOR value + poly = 0x8005 # generator polinom (normal form) + + reg = xor_in + for octet in data: + # reflect in + for i in range(8): + topbit = reg & 0x8000 + if octet & (0x80 >> i): + topbit ^= 0x8000 + reg <<= 1 + if topbit: + reg ^= poly + reg &= 0xFFFF + # reflect out + return reg ^ xor_out + + def getAudioDevices(): + p = pyaudio.PyAudio() + info = p.get_host_api_info_by_index(0) + numdevices = info.get('deviceCount') + 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')) + + +class TX(): + + + def Broadcast(self, bcdata): + + modem = freedv.FreeDV() + audio = sound.Audio() + + + #preamble = b'\x00' * modem.bytes_per_frame * 2 #SET PREAMPLE + preamble = b'\x00\x00\x00' + header = b'' # SET HEADER + #postamble = b'\xFF' * modem.bytes_per_frame# * 2 # SET POSTAMPLE + frame = preamble + header + bcdata# + postamble # COMBINE PREAMPLE, HEADER, DATA AND POSTAMPLE + + logging.info(hex(Helpers.crc16(bcdata))) + logging.info(len(hex(Helpers.crc16(bcdata)))) + #testcrc = modem.c_lib.freedv_gen_crc16(frame,modem.payload_per_frame) + + + logging.info("BYTES PER FRAME: " + str(modem.bytes_per_frame)) + logging.info("PAYLOAD PER FRAME: " + str(modem.payload_per_frame)) + logging.info("FRAME LENGTH: " + str(len(frame))) + #logging.info(str(len(frame)/modem.bytes_per_frame)) + #logging.info(str(len(frame) % modem.bytes_per_frame)) + + + # Check if data is divisable by bytes per frame. If yes (0) skip, If no (>0) fill up with frames + checkframe = len(frame) % modem.bytes_per_frame + + # filecrc = binascii.crc16(frame).to_bytes(2, byteorder='big', signed=False) + + + + + #if checkframe != 0: + # filler = bytes(modem.bytes_per_frame - (len(frame) % modem.bytes_per_frame)) + # logging.info("FILLER: " + str(len(filler))) + # + # frame = frame + filler + # if len(filler) == 6: + # filler * 2 + #frame = frame + filler + #checkframe = len(frame) % modem.bytes_per_frame * 2 + #logging.info("CHECKFRAME: " + str(checkframe)) + #logging.info("FRAME LENGTH: " + str(len(frame))) + + # Pull frame into a datalist to work as a simple buffer + data_list = [frame[i:i+modem.bytes_per_frame*2] for i in range(0, len(frame), modem.bytes_per_frame*2)] # PACK DATAFRAME TO A LIST WHICH IS AS BIG AS THE FRAME SIZE OF FREEDV MODE + + #data_list.append(b'0x00') + + + length = len(data_list) # GET LENGTH OF DATA LIST + + # Loop through data list for every item and modulate it + for i in range(length): # LOOP THROUGH DATA LIST + #crc = Helpers.crc16(data_list[i]) + #logging.info(hex(crc)) + logging.info(data_list[i]) + modulated_data = modem.Modulate(data_list[i]) + #logging.info(bytes(modulated_data)) + audio.Play(modulated_data) + + + + + +class RX(): + + def ReceiveAudio(): + + audio = sound.Audio.Record() + frame = freedv.FreeDV.Demodulate(audio) + logging.info(frame) + + + + + + + + \ No newline at end of file diff --git a/freedv.py b/freedv.py new file mode 100644 index 00000000..4a2ff2ac --- /dev/null +++ b/freedv.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 16:58:35 2020 + +@author: DJ2LS +""" + + +import ctypes +from ctypes import * +import pathlib + +import logging +import sound + + + +class FreeDV(): + + def __init__(self): + + libname = pathlib.Path().absolute() / "libcodec2.so" + self.c_lib = ctypes.CDLL(libname) + + self.audio = sound.Audio() + + + self.freedv = self.c_lib.freedv_open(3) + 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 + + + logging.info("FreeDV Initialized") + + + # MODULATION-OUT OBJECT + def ModulationOut(self): + return (c_short * self.c_lib.freedv_get_n_nom_modem_samples(self.freedv)) + + # MODULATION-IN OBJECT + def ModulationIn(self): + return (c_short * self.c_lib.freedv_get_n_nom_modem_samples(self.freedv)) + + # DataFrame + def DataFrame(self): + return (ctypes.c_short * self.bytes_per_frame) + + # GET DATA AND MODULATE IT + def Modulate(self,data_out): + + mod_out = self.ModulationOut()() # new modulation object and get pointer to it + + data = (ctypes.c_short * self.bytes_per_frame).from_buffer_copy(data_out) + #self.freedv_rawdatapreambletx(self.freedv, mod_out) # SEND PREAMBLE + self.c_lib.freedv_rawdatatx(self.freedv,mod_out,data) # SEND DATA + #logging.info(bytes(mod_out)) + return mod_out + + + # DEMODULATE DATA AND RETURN IT + def Demodulate(self,modulation_in): + + mod_in = self.ModulationIn()() # new modulation object and get pointer to it + data_in = self.DataFrame()() + self.c_lib.freedv_rawdatarx(self.freedv, data_in, mod_in) + + return data_in + diff --git a/sound.py b/sound.py new file mode 100644 index 00000000..77c4cfb4 --- /dev/null +++ b/sound.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 17:47:32 2020 + +@author: DJ2LS +""" + + +import pyaudio +import audioop +import logging + + +class Audio(): + + def __init__(self, + defaultFrames = 1024 * 8, + audio_input_device=2, + audio_output_device=0, + tx_sample_state = None, + rx_sample_state = None, + audio_sample_rate=48000, + modem_sample_rate=8000, + frames_per_buffer=1024, + audio_channels=1, + #format = pyaudio.paInt16, + stream = None, + ): + + self.p = pyaudio.PyAudio() + self.defaultFrames = defaultFrames + self.audio_input_device = audio_input_device + self.audio_output_device = audio_output_device + self.tx_sample_state = tx_sample_state + self.rx_sample_state = rx_sample_state + self.audio_sample_rate = audio_sample_rate + self.modem_sample_rate = modem_sample_rate + self.frames_per_buffer = frames_per_buffer + self.audio_channels = audio_channels + self.format = pyaudio.paInt16 + self.stream = None + + logging.info("AUDIO Initialized") + + + # PLAY MODULATED AUDIO + def Play(self, modulation): + + stream_tx = self.p.open(format=self.format, + channels=self.audio_channels, + rate=self.audio_sample_rate, + frames_per_buffer=self.frames_per_buffer, + output=True, + output_device_index=self.audio_output_device, + ) + + audio = audioop.ratecv(modulation,2,1,self.modem_sample_rate, self.audio_sample_rate, self.tx_sample_state) + stream_tx.write(audio[0]) + stream_tx.close() + #self.p.terminate() + + + # RECORD AUDIO + def Record(self): + + stream_rx = self.p.open(format=self.format, + channels=self.audio_channels, + rate=self.audio_sample_rate, + frames_per_buffer=self.frames_per_buffer, #modem.get_n_max_modem_samples() + input=True, + input_device_index=self.audio_input_device, + ) + + #audio = audioop.ratecv(modulation,2,1,modem_sample_rate, audio_sample_rate, tx_sample_state) + #stream.read(audio[0]) + data = stream_rx.read(self.defaultFrames) + data = audioop.ratecv(data,2,1,self.audio_sample_rate, self.modem_sample_rate, self.rx_sample_state) + #stream_rx.close() + #self.p.terminate() + return data \ No newline at end of file diff --git a/tnc.py b/tnc.py new file mode 100644 index 00000000..5a4d9141 --- /dev/null +++ b/tnc.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 11 20:06:55 2020 + +@author: DJ2LS +""" + +import logging +import socketserver +import freedv +import commands + +class MyTCPHandler(socketserver.BaseRequestHandler): + + + + def handle(self): + # self.request is the TCP socket connected to the client + self.data = self.request.recv(1024).strip() + 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!") + +# BROADCAST PARSER ########################################################### + #if self.data == b'BC': + if self.data.startswith(b'BC:'): + data = self.data.split(b'BC:') + commands.TX.Broadcast(bytes(data[1]),bytes(data[1])) + #commands.TX.Broadcast(data,data) \ No newline at end of file