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 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
2020-12-23 16:48:54 +00:00
class RF ( ) :
2021-01-30 16:25:24 +00:00
def __init__ ( self ) :
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-07 08:28:26 +00:00
#--------------------------------------------CREATE PYAUDIO INSTANCE
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
2021-01-30 16:25:24 +00:00
#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'])
static . AUDIO_SAMPLE_RATE_TX = 8000
2021-02-01 20:46:33 +00:00
static . AUDIO_SAMPLE_RATE_RX = 8000
2021-01-07 08:28:26 +00:00
#--------------------------------------------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-30 16:25:24 +00:00
#--------------------------------------------START DECODER THREAD
FREEDV_DECODER_THREAD = threading . Thread ( target = self . receive , args = [ static . FREEDV_DATA_MODE , static . FREEDV_SIGNALLING_MODE ] , name = " FREEDV_DECODER_THREAD " )
FREEDV_DECODER_THREAD . start ( )
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 ( )
2021-01-20 21:51:14 +00:00
mod_out_preamble = ctypes . c_short * n_tx_modem_samples #1760 for mode 10,11,12 #4000 for mode 9
mod_out_preamble = mod_out_preamble ( )
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
2021-01-20 21:51:14 +00:00
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 = 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
2021-01-21 07:33:45 +00:00
2021-01-05 14:03:41 +00:00
data = ( ctypes . c_ubyte * bytes_per_frame ) . from_buffer_copy ( buffer )
2021-01-20 21:51:14 +00:00
self . c_lib . freedv_rawdatapreambletx ( freedv , mod_out_preamble )
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-16 13:28:47 +00:00
# -------------- preamble area
2021-01-20 21:51:14 +00:00
# WE NEED TO ADJUST IT FOR SINGLE TRANSMISSION
2021-01-16 13:28:47 +00:00
2021-01-20 21:51:14 +00:00
txbuffer = bytearray ( )
txbuffer + = bytes ( mod_out_preamble )
txbuffer + = bytes ( mod_out )
2021-02-01 20:46:33 +00:00
txbuffer = txbuffer . rstrip ( b ' \x00 ' )
2021-02-05 13:40:32 +00:00
2021-01-16 13:28:47 +00:00
# -------------- transmit audio
2021-01-30 16:25:24 +00:00
self . stream_tx . write ( bytes ( txbuffer ) )
2021-02-05 13:40:32 +00:00
2021-01-16 19:43:10 +00:00
#--------------------------------------------------------------------------------------------------------
2021-01-21 07:33:45 +00:00
def transmit_arq_ack ( self , ack_buffer ) :
2021-01-21 20:00:21 +00:00
static . ARQ_STATE = ' SENDING_ACK '
2021-01-21 07:33:45 +00:00
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 ) * 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 ( )
2021-02-01 20:46:33 +00:00
mod_out_preamble = ctypes . c_short * ( 1760 * 2 ) #1760 for mode 10,11,12 #4000 for mode 9
2021-01-30 16:25:24 +00:00
mod_out_preamble = mod_out_preamble ( )
2021-01-21 07:33:45 +00:00
buffer = bytearray ( payload_per_frame ) # use this if CRC16 checksum is required ( DATA1-3)
buffer [ : len ( ack_buffer ) ] = ack_buffer # 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
2021-02-01 20:46:33 +00:00
#print(bytes(buffer))
2021-01-21 07:33:45 +00:00
data = ( ctypes . c_ubyte * bytes_per_frame ) . from_buffer_copy ( buffer )
2021-01-30 16:25:24 +00:00
2021-02-01 20:46:33 +00:00
preamble_bytes = self . c_lib . freedv_rawdatapreambletx ( freedv , mod_out_preamble )
2021-01-21 07:33:45 +00:00
self . c_lib . freedv_rawdatatx ( freedv , mod_out , data ) # modulate DATA and safe it into mod_out pointer
2021-01-30 16:25:24 +00:00
txbuffer = bytearray ( )
txbuffer + = bytes ( mod_out_preamble )
2021-02-01 20:46:33 +00:00
txbuffer = txbuffer . rstrip ( b ' \x00 ' ) #lets remove unallocated memory because of wrong buffer :-/
2021-01-30 16:25:24 +00:00
txbuffer + = bytes ( mod_out )
2021-02-01 20:46:33 +00:00
txbuffer = txbuffer . rstrip ( b ' \x00 ' ) #lets remove unallocated memory because of wrong buffer :-/
# -------------- transmit audio
2021-01-30 16:25:24 +00:00
self . stream_tx . write ( bytes ( txbuffer ) )
2021-02-05 13:40:32 +00:00
self . stream_tx . write ( bytes ( txbuffer ) )
2021-01-21 20:00:21 +00:00
static . ARQ_STATE = ' RECEIVING_DATA '
2021-01-21 07:33:45 +00:00
#--------------------------------------------------------------------------------------------------------
# GET ARQ BURST FRAME VOM BUFFER AND MODULATE IT
def transmit_arq_burst ( self ) :
2021-01-21 20:00:21 +00:00
static . ARQ_STATE = ' SENDING_DATA '
2021-02-04 16:51:01 +00:00
2021-01-16 19:43:10 +00:00
self . c_lib . freedv_open . restype = ctypes . POINTER ( ctypes . c_ubyte )
2021-01-20 21:51:14 +00:00
freedv = self . c_lib . freedv_open ( static . FREEDV_DATA_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
2021-02-01 20:46:33 +00:00
2021-01-16 19:43:10 +00:00
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 ( )
2021-02-01 20:46:33 +00:00
mod_out_preamble = ctypes . c_short * ( n_tx_modem_samples * 2 ) #1760 for mode 10,11,12 #4000 for mode 9
2021-01-20 21:51:14 +00:00
mod_out_preamble = mod_out_preamble ( )
self . c_lib . freedv_rawdatapreambletx ( freedv , mod_out_preamble ) ;
txbuffer = bytearray ( )
txbuffer + = bytes ( mod_out_preamble )
2021-02-04 14:25:15 +00:00
txbuffer = txbuffer . rstrip ( b ' \x00 ' ) #lets remove unallocated memory because of wrong buffer :-/
2021-01-21 20:00:21 +00:00
2021-01-20 21:51:14 +00:00
for n in range ( 0 , static . ARQ_TX_N_FRAMES_PER_BURST ) :
#---------------------------BUILD ARQ BURST ---------------------------------------------------------------------
2021-02-01 20:46:33 +00:00
frame_type = 10 + n + 1 #static.ARQ_TX_N_FRAMES_PER_BURST
2021-01-16 19:43:10 +00:00
frame_type = bytes ( [ frame_type ] )
2021-01-21 20:00:21 +00:00
2021-01-20 21:51:14 +00:00
payload_data = bytes ( static . TX_BUFFER [ static . ARQ_N_SENT_FRAMES + n ] )
2021-02-05 13:40:32 +00:00
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 ' )
# 1 # frame type and current number of arq frame of burst
# 1 # total number of arq frames per (current) burst
# 2 # current arq frame number
# 2 # total number arq frames
# 2 # arq crc
# N # payload data
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 . ARQ_BURST_PAYLOAD_CRC + \
payload_data
2021-01-16 19:43:10 +00:00
2021-02-06 14:11:42 +00:00
#print(arqframe)
2021-01-16 19:43:10 +00:00
2021-01-20 21:51:14 +00:00
buffer = bytearray ( static . FREEDV_DATA_PAYLOAD_PER_FRAME ) # create TX buffer
2021-01-16 19:43:10 +00:00
buffer [ : len ( arqframe ) ] = arqframe # set buffersize to length of data which will be send
2021-01-30 16:25:24 +00:00
2021-01-20 21:51:14 +00:00
crc = ctypes . c_ushort ( self . c_lib . freedv_gen_crc16 ( bytes ( buffer ) , static . FREEDV_DATA_PAYLOAD_PER_FRAME ) ) # generate CRC16
2021-01-16 19:43:10 +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-20 21:51:14 +00:00
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
txbuffer + = bytes ( mod_out )
2021-02-04 14:25:15 +00:00
txbuffer = txbuffer . rstrip ( b ' \x00 ' ) #lets remove unallocated memory because of wrong buffer :-/
2021-01-30 16:25:24 +00:00
2021-01-21 20:00:21 +00:00
# -------------- transmit audio
2021-01-30 16:25:24 +00:00
self . stream_tx . write ( bytes ( txbuffer ) )
2021-01-21 20:00:21 +00:00
static . ARQ_STATE = ' RECEIVING_ACK '
2021-01-21 07:33:45 +00:00
#--------------------------------------------------------------------------------------------------------
2021-01-20 21:51:14 +00:00
def receive ( self , data_mode , signalling_mode ) :
2021-01-16 19:43:10 +00:00
2021-01-05 14:03:41 +00:00
self . c_lib . freedv_open . restype = ctypes . POINTER ( ctypes . c_ubyte )
2021-01-20 21:51:14 +00:00
freedv_data = self . c_lib . freedv_open ( data_mode )
freedv_signalling = self . c_lib . freedv_open ( signalling_mode )
static . FREEDV_DATA_BYTES_PER_FRAME = int ( self . c_lib . freedv_get_bits_per_modem_frame ( freedv_data ) / 8 )
static . FREEDV_DATA_PAYLOAD_PER_FRAME = static . FREEDV_DATA_BYTES_PER_FRAME - 2
static . FREEDV_SIGNALLING_BYTES_PER_FRAME = int ( self . c_lib . freedv_get_bits_per_modem_frame ( freedv_signalling ) / 8 )
static . FREEDV_SIGNALLING_PAYLOAD_PER_FRAME = static . FREEDV_SIGNALLING_BYTES_PER_FRAME - 2
data_bytes_out = ( ctypes . c_ubyte * static . FREEDV_DATA_BYTES_PER_FRAME )
data_bytes_out = data_bytes_out ( ) #get pointer from bytes_out
signalling_bytes_out = ( ctypes . c_ubyte * static . FREEDV_SIGNALLING_BYTES_PER_FRAME )
signalling_bytes_out = signalling_bytes_out ( ) #get pointer from bytes_out
2021-02-05 13:40:32 +00:00
# with this we can interrupt receiving
while static . FREEDV_RECEIVE == True :
2021-01-20 21:51:14 +00:00
time . sleep ( 0.01 )
2021-01-06 12:17:17 +00:00
2021-02-06 14:11:42 +00:00
# stuck in sync counter
stuck_in_sync_counter = 0
stuck_in_sync_10_counter = 0
#
2021-01-20 21:51:14 +00:00
while static . ARQ_STATE == ' RECEIVING_DATA ' :
time . sleep ( 0.01 )
nin = self . c_lib . freedv_nin ( freedv_data )
nin = int ( nin * ( static . AUDIO_SAMPLE_RATE_RX / static . MODEM_SAMPLE_RATE ) )
data_in = self . stream_rx . read ( nin , exception_on_overflow = False )
2021-01-30 16:40:03 +00:00
data_in = data_in . rstrip ( b ' \x00 ' )
2021-01-21 20:00:21 +00:00
#print(data_in)
2021-01-20 21:51:14 +00:00
self . c_lib . freedv_rawdatarx . argtype = [ ctypes . POINTER ( ctypes . c_ubyte ) , data_bytes_out , data_in ] # check if really neccessary
nbytes = self . c_lib . freedv_rawdatarx ( freedv_data , data_bytes_out , data_in ) # demodulate audio
2021-02-04 16:51:01 +00:00
#print(self.c_lib.freedv_get_rx_status(freedv_data))
2021-01-21 20:00:21 +00:00
2021-02-06 14:11:42 +00:00
#-------------STUCK IN SYNC DETECTOR
stuck_in_sync_counter + = 1
if self . c_lib . freedv_get_rx_status ( freedv_data ) == 10 :
stuck_in_sync_10_counter + = 1
#print(stuck_in_sync_counter)
if stuck_in_sync_counter == 33 and self . c_lib . freedv_get_rx_status ( freedv_data ) == 10 :
print ( " stuck in sync #1 --> DOING UNSYNC " )
self . c_lib . freedv_set_sync ( freedv_data , 0 ) #FORCE UNSYNC
stuck_in_sync_counter = 0
stuck_in_sync_10_counter = 0
data_in = None
if stuck_in_sync_counter > = 66 and stuck_in_sync_10_counter > = 2 :
print ( " stuck in sync #2 --> DOING UNSYNC " )
self . c_lib . freedv_set_sync ( freedv_data , 0 ) #FORCE UNSYNC
stuck_in_sync_counter = 0
stuck_in_sync_10_counter = 0
data_in = None
#-----------------------------------
2021-01-30 16:25:24 +00:00
#modem_stats_snr = c_float()
#modem_stats_sync = c_int()
2021-01-21 20:00:21 +00:00
#self.c_lib.freedv_get_modem_stats(freedv_data,byref(modem_stats_sync), byref(modem_stats_snr))
#modem_stats_snr = modem_stats_snr.value
#print(modem_stats_snr)
2021-01-20 21:51:14 +00:00
if nbytes == static . FREEDV_DATA_BYTES_PER_FRAME :
2021-02-06 14:11:42 +00:00
# counter reset for stuck in sync counter
stuck_in_sync_counter = 0
stuck_in_sync_10_counter = 0
#
2021-02-05 13:40:32 +00:00
2021-01-20 21:51:14 +00:00
# CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------
2021-02-01 20:46:33 +00:00
frametype = int . from_bytes ( bytes ( data_bytes_out [ : 1 ] ) , " big " )
frame = frametype - 10
n_frames_per_burst = int . from_bytes ( bytes ( data_bytes_out [ 1 : 2 ] ) , " big " )
2021-01-20 21:51:14 +00:00
if 50 > = frametype > = 10 and len ( data_bytes_out ) > 30 : # --> The length check filters out random strings without CRC
2021-02-04 16:51:01 +00:00
arq . data_received ( bytes ( data_bytes_out [ : - 2 ] ) ) #send payload data to arq checker without CRC16
2021-01-20 21:51:14 +00:00
else :
2021-02-01 20:46:33 +00:00
print ( " MODE: " + str ( data_mode ) + " DATA: " + str ( bytes ( data_bytes_out ) ) )
2021-01-21 20:00:21 +00:00
# NEEDS TO BE OPTIMIZED
2021-02-04 14:25:15 +00:00
# DO UNSYNC AFTER LAST BURST by checking the frame numbers agains the total frames per burst
2021-02-01 20:46:33 +00:00
if frame == n_frames_per_burst :
self . c_lib . freedv_set_sync ( freedv_data , 0 ) #FORCE UNSYNC
2021-01-21 20:00:21 +00:00
2021-02-04 14:25:15 +00:00
# DETECT STUCK IN SYNC
# count rx status 10
# if 10 greater 2 after 6 then unsync
2021-01-20 21:51:14 +00:00
while static . ARQ_STATE == ' IDLE ' or static . ARQ_STATE == ' RECEIVING_ACK ' :
time . sleep ( 0.01 )
2021-01-21 20:00:21 +00:00
2021-01-20 21:51:14 +00:00
nin = self . c_lib . freedv_nin ( freedv_signalling )
nin = int ( nin * ( static . AUDIO_SAMPLE_RATE_RX / static . MODEM_SAMPLE_RATE ) )
data_in = self . stream_rx . read ( nin , exception_on_overflow = False )
2021-01-30 16:48:25 +00:00
data_in = data_in . rstrip ( b ' \x00 ' )
2020-12-28 14:20:51 +00:00
2021-01-20 21:51:14 +00:00
self . c_lib . freedv_rawdatarx . argtype = [ ctypes . POINTER ( ctypes . c_ubyte ) , signalling_bytes_out , data_in ] # check if really neccessary
nbytes = self . c_lib . freedv_rawdatarx ( freedv_signalling , signalling_bytes_out , data_in ) # demodulate audio
2021-02-01 20:46:33 +00:00
# CHECK IF FRAME CONTAINS ACK------------------------
2021-01-20 21:51:14 +00:00
frametype = int . from_bytes ( bytes ( signalling_bytes_out [ : 1 ] ) , " big " )
2021-02-06 14:11:42 +00:00
# BURST ACK
2021-02-01 20:46:33 +00:00
if frametype == 60 and nbytes == static . FREEDV_SIGNALLING_BYTES_PER_FRAME :
2021-02-06 14:11:42 +00:00
arq . burst_ack_received ( )
# FRAME ACK
if frametype == 61 and nbytes == static . FREEDV_SIGNALLING_BYTES_PER_FRAME :
arq . frame_ack_received ( )
2021-02-04 16:51:01 +00:00
rxstatus = self . c_lib . freedv_get_rx_status ( freedv_signalling )
2021-02-06 14:11:42 +00:00
#print(rxstatus)
2021-02-01 20:46:33 +00:00
if nbytes == static . FREEDV_SIGNALLING_BYTES_PER_FRAME or rxstatus == 10 :
self . c_lib . freedv_set_sync ( freedv_signalling , 0 ) #FORCE UNSYNC
2021-01-20 21:51:14 +00:00