2020-12-23 16:48:54 +00:00
#!/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 sys
2020-12-26 18:27:09 +00:00
import logging
2021-01-06 12:17:17 +00:00
import time
import threading
2020-12-23 16:48:54 +00:00
2021-01-06 12:17:17 +00:00
import helpers
2020-12-23 16:48:54 +00:00
import static
2020-12-27 21:38:49 +00:00
import arq
#arq = arq.ARQ()
2020-12-23 16:48:54 +00:00
class RF ( ) :
def __init__ ( self ) :
2020-12-28 14:20:51 +00:00
2021-01-07 08:28:26 +00:00
#-------------------------------------------- LOAD FREEDV
2020-12-26 10:02:14 +00:00
libname = pathlib . Path ( ) . absolute ( ) / " codec2/build_linux/src/libcodec2.so "
2020-12-23 16:48:54 +00:00
self . c_lib = ctypes . CDLL ( libname )
2021-01-06 12:17:17 +00:00
2021-01-07 08:28:26 +00:00
#--------------------------------------------CREATE PYAUDIO INSTANCE
2020-12-23 16:48:54 +00:00
2021-01-05 14:03:41 +00:00
self . p = pyaudio . PyAudio ( )
2021-01-07 08:28:26 +00:00
#--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE
static . AUDIO_SAMPLE_RATE_RX = int ( self . p . get_device_info_by_index ( static . AUDIO_INPUT_DEVICE ) [ ' defaultSampleRate ' ] )
static . AUDIO_SAMPLE_RATE_TX = int ( self . p . get_device_info_by_index ( static . AUDIO_OUTPUT_DEVICE ) [ ' defaultSampleRate ' ] )
#--------------------------------------------OPEN AUDIO CHANNEL RX
2021-01-05 14:03:41 +00:00
self . stream_rx = self . p . open ( format = pyaudio . paInt16 ,
channels = static . AUDIO_CHANNELS ,
2021-01-07 08:28:26 +00:00
rate = static . AUDIO_SAMPLE_RATE_RX ,
2021-01-06 12:17:17 +00:00
frames_per_buffer = static . AUDIO_FRAMES_PER_BUFFER ,
2021-01-05 14:03:41 +00:00
input = True ,
input_device_index = static . AUDIO_INPUT_DEVICE ,
)
2021-01-06 12:17:17 +00:00
#--------------------------------------------OPEN AUDIO CHANNEL TX
2021-01-06 17:01:54 +00:00
self . stream_tx = self . p . open ( format = pyaudio . paInt16 ,
channels = 1 ,
2021-01-07 08:28:26 +00:00
rate = static . AUDIO_SAMPLE_RATE_TX ,
2021-01-06 17:01:54 +00:00
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
)
2021-01-07 08:28:26 +00:00
2021-01-06 12:17:17 +00:00
#--------------------------------------------START AUDIO THREAD
2020-12-23 16:48:54 +00:00
2021-01-06 12:17:17 +00:00
AUDIO_LISTEN_THREAD = threading . Thread ( target = self . audio_listen , name = " Audio Listener " )
AUDIO_LISTEN_THREAD . start ( )
#--------------------------------------------START DECODER THREADS
FREEDV_700D_THREAD = threading . Thread ( target = self . receive , args = [ 7 ] , name = " 700D Decoder " )
FREEDV_700D_THREAD . start ( )
FREEDV_DATAC1_THREAD = threading . Thread ( target = self . receive , args = [ 10 ] , name = " DATAC1 Decoder " )
FREEDV_DATAC1_THREAD . start ( )
FREEDV_DATAC2_THREAD = threading . Thread ( target = self . receive , args = [ 11 ] , name = " DATAC2 Decoder " )
FREEDV_DATAC2_THREAD . start ( )
2021-01-06 17:01:54 +00:00
2021-01-06 12:17:17 +00:00
FREEDV_DATAC3_THREAD = threading . Thread ( target = self . receive , args = [ 12 ] , name = " DATAC3 Decoder " )
FREEDV_DATAC3_THREAD . start ( )
2021-01-06 17:01:54 +00:00
#time.sleep(2)
#self.transmit(7,b'000000000000')
#self.transmit(7,b'ABCDEFGHIJKL')
2021-01-06 12:17:17 +00:00
#--------------------------------------------------------------------------------------------------------
2021-01-06 17:01:54 +00:00
2021-01-06 12:17:17 +00:00
def audio_listen ( self ) :
print ( " STARTING AUDIO LISTENER " )
while True :
time . sleep ( 0.05 )
data = self . stream_rx . read ( static . AUDIO_FRAMES_PER_BUFFER , exception_on_overflow = False )
2021-01-06 17:01:54 +00:00
#static.AUDIO_BUFFER += data
static . AUDIO_BUFFER + = data . strip ( b ' \x00 ' )
rms = audioop . rms ( data , 2 )
print ( rms )
2021-01-06 12:17:17 +00:00
#--------------------------------------------------------------------------------------------------------
2020-12-23 16:48:54 +00:00
# GET DATA AND MODULATE IT
2020-12-27 21:38:49 +00:00
2021-01-06 12:17:17 +00:00
def transmit ( self , mode , data_out ) :
2021-01-05 14:03:41 +00:00
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 ( )
2020-12-28 14:20:51 +00:00
2021-01-06 17:01:54 +00:00
if mode < 10 :
##preamble = bytes(payload_per_frame)
preamble = b ' 111111111111 '
data_out = preamble + data_out
#data_out += data_out
2021-01-05 14:03:41 +00:00
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
2020-12-28 14:20:51 +00:00
data_list_length = len ( data_list )
for i in range ( data_list_length ) : # LOOP THROUGH DATA LIST
2020-12-23 16:48:54 +00:00
2021-01-05 14:03:41 +00:00
if mode < 10 : # don't generate CRC16 for modes 0 - 9
2021-01-06 17:01:54 +00:00
2021-01-05 14:03:41 +00:00
buffer = bytearray ( bytes_per_frame ) # use this if no CRC16 checksum is required
2020-12-28 14:20:51 +00:00
buffer [ : len ( data_list [ i ] ) ] = data_list [ i ] # set buffersize to length of data which will be send
2021-01-06 12:17:17 +00:00
print ( " buffer for ACK: " + str ( buffer ) )
2021-01-05 14:03:41 +00:00
if mode > = 10 : #generate CRC16 for modes 10-12..
2020-12-27 21:38:49 +00:00
2021-01-05 14:03:41 +00:00
buffer = bytearray ( payload_per_frame ) # use this if CRC16 checksum is required ( DATA1-3)
2020-12-28 14:20:51 +00:00
buffer [ : len ( data_list [ i ] ) ] = data_list [ i ] # set buffersize to length of data which will be send
2021-01-05 14:03:41 +00:00
crc = ctypes . c_ushort ( self . c_lib . freedv_gen_crc16 ( bytes ( buffer ) , payload_per_frame ) ) # generate CRC16
2020-12-28 14:20:51 +00:00
crc = crc . value . to_bytes ( 2 , byteorder = ' big ' ) # convert crc to 2 byte hex string
buffer + = crc # append crc16 to buffer
2021-01-05 14:03:41 +00:00
data = ( ctypes . c_ubyte * bytes_per_frame ) . from_buffer_copy ( buffer )
2020-12-23 16:48:54 +00:00
2021-01-05 14:03:41 +00:00
self . c_lib . freedv_rawdatatx ( freedv , mod_out , data ) # modulate DATA and safe it into mod_out pointer
2021-01-06 17:01:54 +00:00
#print(bytes(mod_out).strip(b'\x00'))
2021-01-05 14:03:41 +00:00
2021-01-06 17:01:54 +00:00
#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=1, #static.AUDIO_OUTPUT_DEVICE
# )
2020-12-23 16:48:54 +00:00
2021-01-07 08:28:26 +00:00
audio = audioop . ratecv ( mod_out , 2 , 1 , static . MODEM_SAMPLE_RATE , static . AUDIO_SAMPLE_RATE_TX , static . TX_SAMPLE_STATE )
2021-01-06 17:01:54 +00:00
self . stream_tx . write ( audio [ 0 ] )
2021-01-05 14:03:41 +00:00
2021-01-06 17:01:54 +00:00
#print("KILL")
#stream_tx.stop_stream()
#stream_tx.close()
#p.terminate()
2020-12-23 16:48:54 +00:00
return mod_out
2021-01-06 12:17:17 +00:00
#--------------------------------------------------------------------------------------------------------
2020-12-23 16:48:54 +00:00
# DEMODULATE DATA AND RETURN IT
2021-01-06 12:17:17 +00:00
def receive ( self , mode ) :
2020-12-23 16:48:54 +00:00
2021-01-06 12:17:17 +00:00
print ( " STARTING MODE: " + str ( mode ) )
static . MODEM_RECEIVE = True
2020-12-23 16:48:54 +00:00
2021-01-05 14:03:41 +00:00
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 )
2020-12-23 16:48:54 +00:00
2021-01-05 14:03:41 +00:00
bytes_out = ( ctypes . c_ubyte * bytes_per_frame )
bytes_out = bytes_out ( ) #get pointer from bytes_out
2021-01-06 12:17:17 +00:00
i = 0
2021-01-06 17:01:54 +00:00
while True : # Listne to audio until data arrives
2021-01-06 12:17:17 +00:00
time . sleep ( 0.05 ) # here we reduce CPU load
2021-01-05 14:03:41 +00:00
nin = self . c_lib . freedv_nin ( freedv )
2021-01-06 12:17:17 +00:00
#data_in = self.stream_rx.read(nin, exception_on_overflow = False)
#print(len(data_in))
#nbytes = self.c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # Demodulated data and get number of demodulated bytes
data_in = static . AUDIO_BUFFER
data_in = bytes ( data_in )
try :
data_in = bytes ( data_in )
data = data_in [ i : ( ( nin * 2 ) + i ) ] # * 2 because of byte size per audio frame ( 2bytes / 16bit?)
self . c_lib . freedv_rawdatarx . argtype = [ ctypes . POINTER ( ctypes . c_ubyte ) , bytes_out , data ] # check if really neccessary
nbytes = self . c_lib . freedv_rawdatarx ( freedv , bytes_out , data ) # demodulate audio
except IndexError :
data_in = b ' \x00 '
2020-12-25 21:55:56 +00:00
2021-01-05 14:03:41 +00:00
if nbytes == bytes_per_frame : # make sure, we receive a full frame
2021-01-06 12:17:17 +00:00
print ( " MODE: " + str ( mode ) + " DATA: " + str ( bytes ( bytes_out ) ) )
# --------------- DEBUGGING OUTPTUT -------------------------------------------
#if mode >= 10:
#
# print("MODE: " + str(mode) + " DATA: " + str(bytes(bytes_out[:-2])))
# static.AUDIO_BUFFER = bytearray()
#else:
# print("MODE: " + str(mode) + " DATA: " + str(bytes(bytes_out)))
# --------------- END DEBUGGING OUTPTUT -------------------------------------------
2020-12-27 21:38:49 +00:00
2021-01-06 12:17:17 +00:00
# CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------
2020-12-27 21:38:49 +00:00
frametype = int . from_bytes ( bytes ( bytes_out [ : 1 ] ) , " big " )
2021-01-06 12:17:17 +00:00
if 50 > = frametype > = 10 and len ( bytes_out ) > 30 : # --> The length check filters out random strings without CRC
static . AUDIO_BUFFER = bytearray ( )
print ( " MODE: " + str ( mode ) + " DATA: " + str ( bytes ( bytes_out [ : - 2 ] ) ) )
2020-12-28 14:20:51 +00:00
arq . data_received ( bytes ( bytes_out [ : - 2 ] ) ) #send payload data to arq checker without CRC16
2021-01-06 17:01:54 +00:00
self . c_lib . freedv_set_sync ( freedv , 0 ) #FORCE UNSYNC
2021-01-06 12:17:17 +00:00
else :
print ( " MODE: " + str ( mode ) + " DATA: " + str ( bytes ( bytes_out ) ) )
2020-12-28 14:20:51 +00:00
2021-01-06 12:17:17 +00:00
# CHECK IF FRAME CONTAINS ACK------------------------ --> 700D / 7
2020-12-25 21:55:56 +00:00
2020-12-23 16:48:54 +00:00
2021-01-06 12:17:17 +00:00
frametype = int . from_bytes ( bytes ( bytes_out [ : 1 ] ) , " big " )
if frametype == 60 and len ( bytes_out ) == 14 :
print ( " ACK FRAME RECEIVED!!!!!!!!!! " )
#if bytes(bytes_out[:1]) == b'<': #b'\7': < = 60
# CHECK CRC 8 OF ACK FRAME
print ( bytes_out [ : 1 ] )
print ( bytes_out [ 3 : 14 ] )
if bytes ( bytes_out [ : 2 ] ) == helpers . get_crc_8 ( bytes ( bytes_out [ 3 : 14 ] ) ) :
print ( " MODE: " + str ( mode ) + " DATA: " + str ( bytes ( bytes_out ) ) )
arq . ack_received ( )
# ------------------ OUR NICE ITERATOR MACHINE
if len ( static . AUDIO_BUFFER ) > i : # WE WILL LOOP THROUGH OUR DATA BUFFER WHILE OUR BUFFER IS BIGGER THAN THE CHUNK POSITION
i = ( nin * 2 ) + i
else :
2021-01-06 17:01:54 +00:00
i = 0