mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Second run reducing number of problems
This commit is contained in:
parent
b6face744b
commit
d992fd8dc0
|
@ -80,8 +80,12 @@ def get_crc_24(data) -> bytes:
|
||||||
Returns:
|
Returns:
|
||||||
CRC-24 (OpenPGP) of the provided data as bytes
|
CRC-24 (OpenPGP) of the provided data as bytes
|
||||||
"""
|
"""
|
||||||
crc_algorithm = crcengine.create(0x864cfb, 24, 0xb704ce, ref_in=False,
|
crc_algorithm = crcengine.create(0x864cfb,
|
||||||
ref_out=False, xor_out=0,
|
24,
|
||||||
|
0xb704ce,
|
||||||
|
ref_in=False,
|
||||||
|
ref_out=False,
|
||||||
|
xor_out=0,
|
||||||
name='crc-24-openpgp')
|
name='crc-24-openpgp')
|
||||||
crc_data = crc_algorithm(data)
|
crc_data = crc_algorithm(data)
|
||||||
crc_data = crc_data.to_bytes(3, byteorder='big')
|
crc_data = crc_data.to_bytes(3, byteorder='big')
|
||||||
|
@ -136,6 +140,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
|
||||||
static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency])
|
static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
# for idx, item in enumerate(static.HEARD_STATIONS):
|
# for idx, item in enumerate(static.HEARD_STATIONS):
|
||||||
# if dxcallsign in item:
|
# if dxcallsign in item:
|
||||||
# item = [dxcallsign, int(time.time())]
|
# item = [dxcallsign, int(time.time())]
|
||||||
|
@ -174,7 +179,6 @@ def callsign_to_bytes(callsign) -> bytes:
|
||||||
callsign = bytes(callsign, 'utf-8')
|
callsign = bytes(callsign, 'utf-8')
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Exception converting callsign to bytes:", e=e)
|
structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Exception converting callsign to bytes:", e=e)
|
||||||
pass
|
|
||||||
|
|
||||||
# Need this step to reduce the needed payload by the callsign (stripping "-" out of the callsign)
|
# Need this step to reduce the needed payload by the callsign (stripping "-" out of the callsign)
|
||||||
callsign = callsign.split(b'-')
|
callsign = callsign.split(b'-')
|
||||||
|
@ -195,6 +199,7 @@ def callsign_to_bytes(callsign) -> bytes:
|
||||||
return encode_call(callsign + ssid)
|
return encode_call(callsign + ssid)
|
||||||
# return bytes(bytestring)
|
# return bytes(bytestring)
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_callsign(bytestring: bytes) -> bytes:
|
def bytes_to_callsign(bytestring: bytes) -> bytes:
|
||||||
"""
|
"""
|
||||||
Convert our callsign, received by a frame to a callsign in a human readable format
|
Convert our callsign, received by a frame to a callsign in a human readable format
|
||||||
|
@ -311,6 +316,7 @@ def encode_grid(grid):
|
||||||
|
|
||||||
return out_code_word.to_bytes(length=4, byteorder='big')
|
return out_code_word.to_bytes(length=4, byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
def decode_grid(b_code_word: bytes):
|
def decode_grid(b_code_word: bytes):
|
||||||
"""
|
"""
|
||||||
@author: DB1UJ
|
@author: DB1UJ
|
||||||
|
@ -340,6 +346,7 @@ def decode_grid(b_code_word:bytes):
|
||||||
|
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
|
|
||||||
def encode_call(call):
|
def encode_call(call):
|
||||||
"""
|
"""
|
||||||
@author: DB1UJ
|
@author: DB1UJ
|
||||||
|
|
|
@ -24,7 +24,7 @@ import log_handler
|
||||||
import modem
|
import modem
|
||||||
import static
|
import static
|
||||||
|
|
||||||
# signal handler for closing aplication
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
"""
|
"""
|
||||||
a signal handler, which closes the network/socket when closing the application
|
a signal handler, which closes the network/socket when closing the application
|
||||||
|
@ -39,10 +39,11 @@ def signal_handler(sig, frame):
|
||||||
sock.CLOSE_SIGNAL = True
|
sock.CLOSE_SIGNAL = True
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# we need to run this on windows for multiprocessing support
|
# we need to run this on Windows for multiprocessing support
|
||||||
multiprocessing.freeze_support()
|
multiprocessing.freeze_support()
|
||||||
# --------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
PARSER = argparse.ArgumentParser(description='FreeDATA TNC')
|
PARSER = argparse.ArgumentParser(description='FreeDATA TNC')
|
||||||
|
|
100
tnc/modem.py
100
tnc/modem.py
|
@ -10,11 +10,8 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import ctypes
|
import ctypes
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
|
||||||
import queue
|
import queue
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
@ -25,11 +22,9 @@ import sounddevice as sd
|
||||||
import structlog
|
import structlog
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
import audio
|
# import audio
|
||||||
import codec2
|
import codec2
|
||||||
import data_handler
|
import data_handler
|
||||||
import helpers
|
|
||||||
import log_handler
|
|
||||||
import sock
|
import sock
|
||||||
import static
|
import static
|
||||||
|
|
||||||
|
@ -47,8 +42,10 @@ RECEIVE_DATAC1 = False
|
||||||
RECEIVE_DATAC3 = False
|
RECEIVE_DATAC3 = False
|
||||||
RECEIVE_FSK_LDPC_1 = False
|
RECEIVE_FSK_LDPC_1 = False
|
||||||
|
|
||||||
class RF():
|
|
||||||
|
class RF:
|
||||||
""" """
|
""" """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.sampler_avg = 0
|
self.sampler_avg = 0
|
||||||
|
@ -86,38 +83,48 @@ class RF():
|
||||||
|
|
||||||
# Open codec2 instances
|
# Open codec2 instances
|
||||||
self.datac0_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p)
|
self.datac0_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p)
|
||||||
self.c_lib.freedv_set_tuning_range(self.datac0_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX))
|
self.c_lib.freedv_set_tuning_range(self.datac0_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||||
|
ctypes.c_float(static.TUNING_RANGE_FMAX))
|
||||||
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8)
|
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8)
|
||||||
self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2
|
self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2
|
||||||
self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.datac0_freedv)
|
self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.datac0_freedv)
|
||||||
self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(self.datac0_freedv)
|
self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(self.datac0_freedv)
|
||||||
self.datac0_n_tx_preamble_modem_samples = self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv)
|
self.datac0_n_tx_preamble_modem_samples = self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv)
|
||||||
self.datac0_n_tx_postamble_modem_samples = self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv)
|
self.datac0_n_tx_postamble_modem_samples = self.c_lib.freedv_get_n_tx_postamble_modem_samples(
|
||||||
|
self.datac0_freedv)
|
||||||
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
|
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv, 1)
|
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv, 1)
|
||||||
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||||
|
|
||||||
self.datac1_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p)
|
self.datac1_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p)
|
||||||
self.c_lib.freedv_set_tuning_range(self.datac1_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX))
|
self.c_lib.freedv_set_tuning_range(self.datac1_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||||
|
ctypes.c_float(static.TUNING_RANGE_FMAX))
|
||||||
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8)
|
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8)
|
||||||
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
|
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, 1)
|
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, 1)
|
||||||
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||||
|
|
||||||
self.datac3_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p)
|
self.datac3_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p)
|
||||||
self.c_lib.freedv_set_tuning_range(self.datac3_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX))
|
self.c_lib.freedv_set_tuning_range(self.datac3_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||||
|
ctypes.c_float(static.TUNING_RANGE_FMAX))
|
||||||
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8)
|
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8)
|
||||||
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
|
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, 1)
|
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, 1)
|
||||||
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||||
|
|
||||||
self.fsk_ldpc_freedv_0 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), ctypes.c_void_p)
|
self.fsk_ldpc_freedv_0 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||||
|
ctypes.byref(
|
||||||
|
codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)),
|
||||||
|
ctypes.c_void_p)
|
||||||
self.fsk_ldpc_bytes_per_frame_0 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8)
|
self.fsk_ldpc_bytes_per_frame_0 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8)
|
||||||
self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_0)
|
self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_0)
|
||||||
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
||||||
self.fsk_ldpc_buffer_0 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
|
self.fsk_ldpc_buffer_0 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||||
|
|
||||||
self.fsk_ldpc_freedv_1 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), ctypes.c_void_p)
|
self.fsk_ldpc_freedv_1 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||||
|
ctypes.byref(
|
||||||
|
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)),
|
||||||
|
ctypes.c_void_p)
|
||||||
self.fsk_ldpc_bytes_per_frame_1 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8)
|
self.fsk_ldpc_bytes_per_frame_1 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8)
|
||||||
self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_1)
|
self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_1)
|
||||||
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
||||||
|
@ -132,7 +139,9 @@ class RF():
|
||||||
# --------------------------------------------CREATE PYAUDIO INSTANCE
|
# --------------------------------------------CREATE PYAUDIO INSTANCE
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
try:
|
try:
|
||||||
self.stream = sd.RawStream(channels=1, dtype='int16', callback=self.callback, device=(static.AUDIO_INPUT_DEVICE, static.AUDIO_OUTPUT_DEVICE), samplerate = self.AUDIO_SAMPLE_RATE_RX, blocksize=4800)
|
self.stream = sd.RawStream(channels=1, dtype='int16', callback=self.callback,
|
||||||
|
device=(static.AUDIO_INPUT_DEVICE, static.AUDIO_OUTPUT_DEVICE),
|
||||||
|
samplerate=self.AUDIO_SAMPLE_RATE_RX, blocksize=4800)
|
||||||
atexit.register(self.stream.stop)
|
atexit.register(self.stream.stop)
|
||||||
structlog.get_logger("structlog").info("[MDM] init: opened audio devices")
|
structlog.get_logger("structlog").info("[MDM] init: opened audio devices")
|
||||||
|
|
||||||
|
@ -152,6 +161,7 @@ class RF():
|
||||||
# create a stream object for simulating audio stream
|
# create a stream object for simulating audio stream
|
||||||
class Object(object):
|
class Object(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.stream = Object()
|
self.stream = Object()
|
||||||
self.stream.active = True
|
self.stream.active = True
|
||||||
|
|
||||||
|
@ -161,12 +171,13 @@ class RF():
|
||||||
os.mkfifo(TXCHANNEL)
|
os.mkfifo(TXCHANNEL)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
structlog.get_logger("structlog").error(f"[MDM] init:mkfifo: Exception: {e}")
|
structlog.get_logger("structlog").error(f"[MDM] init:mkfifo: Exception: {e}")
|
||||||
pass
|
|
||||||
|
|
||||||
mkfifo_write_callback_thread = threading.Thread(target=self.mkfifo_write_callback, name="MKFIFO WRITE CALLBACK THREAD",daemon=True)
|
mkfifo_write_callback_thread = threading.Thread(target=self.mkfifo_write_callback,
|
||||||
|
name="MKFIFO WRITE CALLBACK THREAD", daemon=True)
|
||||||
mkfifo_write_callback_thread.start()
|
mkfifo_write_callback_thread.start()
|
||||||
|
|
||||||
mkfifo_read_callback_thread = threading.Thread(target=self.mkfifo_read_callback, name="MKFIFO READ CALLBACK THREAD",daemon=True)
|
mkfifo_read_callback_thread = threading.Thread(target=self.mkfifo_read_callback,
|
||||||
|
name="MKFIFO READ CALLBACK THREAD", daemon=True)
|
||||||
mkfifo_read_callback_thread.start()
|
mkfifo_read_callback_thread.start()
|
||||||
|
|
||||||
# --------------------------------------------INIT AND OPEN HAMLIB
|
# --------------------------------------------INIT AND OPEN HAMLIB
|
||||||
|
@ -183,7 +194,11 @@ class RF():
|
||||||
import rigdummy as rig
|
import rigdummy as rig
|
||||||
|
|
||||||
self.hamlib = rig.radio()
|
self.hamlib = rig.radio()
|
||||||
self.hamlib.open_rig(devicename=static.HAMLIB_DEVICE_NAME, deviceport=static.HAMLIB_DEVICE_PORT, hamlib_ptt_type=static.HAMLIB_PTT_TYPE, serialspeed=static.HAMLIB_SERIAL_SPEED, pttport=static.HAMLIB_PTT_PORT, data_bits=static.HAMLIB_DATA_BITS, stop_bits=static.HAMLIB_STOP_BITS, handshake=static.HAMLIB_HANDSHAKE, rigctld_ip = static.HAMLIB_RIGCTLD_IP, rigctld_port = static.HAMLIB_RIGCTLD_PORT)
|
self.hamlib.open_rig(devicename=static.HAMLIB_DEVICE_NAME, deviceport=static.HAMLIB_DEVICE_PORT,
|
||||||
|
hamlib_ptt_type=static.HAMLIB_PTT_TYPE, serialspeed=static.HAMLIB_SERIAL_SPEED,
|
||||||
|
pttport=static.HAMLIB_PTT_PORT, data_bits=static.HAMLIB_DATA_BITS,
|
||||||
|
stop_bits=static.HAMLIB_STOP_BITS, handshake=static.HAMLIB_HANDSHAKE,
|
||||||
|
rigctld_ip=static.HAMLIB_RIGCTLD_IP, rigctld_port=static.HAMLIB_RIGCTLD_PORT)
|
||||||
|
|
||||||
# --------------------------------------------START DECODER THREAD
|
# --------------------------------------------START DECODER THREAD
|
||||||
if static.ENABLE_FFT:
|
if static.ENABLE_FFT:
|
||||||
|
@ -200,10 +215,12 @@ class RF():
|
||||||
audio_thread_datac3.start()
|
audio_thread_datac3.start()
|
||||||
|
|
||||||
if static.ENABLE_FSK:
|
if static.ENABLE_FSK:
|
||||||
audio_thread_fsk_ldpc0 = threading.Thread(target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True)
|
audio_thread_fsk_ldpc0 = threading.Thread(target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0",
|
||||||
|
daemon=True)
|
||||||
audio_thread_fsk_ldpc0.start()
|
audio_thread_fsk_ldpc0.start()
|
||||||
|
|
||||||
audio_thread_fsk_ldpc1 = threading.Thread(target=self.audio_fsk_ldpc_1, name="AUDIO_THREAD FSK LDPC1", daemon=True)
|
audio_thread_fsk_ldpc1 = threading.Thread(target=self.audio_fsk_ldpc_1, name="AUDIO_THREAD FSK LDPC1",
|
||||||
|
daemon=True)
|
||||||
audio_thread_fsk_ldpc1.start()
|
audio_thread_fsk_ldpc1.start()
|
||||||
|
|
||||||
hamlib_thread = threading.Thread(target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True)
|
hamlib_thread = threading.Thread(target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True)
|
||||||
|
@ -251,7 +268,6 @@ class RF():
|
||||||
|
|
||||||
else:
|
else:
|
||||||
data_out48k = self.modoutqueue.popleft()
|
data_out48k = self.modoutqueue.popleft()
|
||||||
#print(len(data_out48k))
|
|
||||||
|
|
||||||
fifo_write = open(TXCHANNEL, 'wb')
|
fifo_write = open(TXCHANNEL, 'wb')
|
||||||
fifo_write.write(data_out48k)
|
fifo_write.write(data_out48k)
|
||||||
|
@ -312,7 +328,7 @@ class RF():
|
||||||
static.BUFFER_OVERFLOW_COUNTER[4] += 1
|
static.BUFFER_OVERFLOW_COUNTER[4] += 1
|
||||||
|
|
||||||
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
||||||
# if not self.modoutqueue or self.mod_out_locked:
|
|
||||||
data_out48k = np.zeros(frames, dtype=np.int16)
|
data_out48k = np.zeros(frames, dtype=np.int16)
|
||||||
self.fft_data = x
|
self.fft_data = x
|
||||||
else:
|
else:
|
||||||
|
@ -390,7 +406,8 @@ class RF():
|
||||||
|
|
||||||
# Create crc for data frame - we are using the crc function shipped with codec2 to avoid
|
# Create crc for data frame - we are using the crc function shipped with codec2 to avoid
|
||||||
# CRC algorithm incompatibilities
|
# CRC algorithm incompatibilities
|
||||||
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # Generate CRC16
|
crc = ctypes.c_ushort(
|
||||||
|
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # Generate CRC16
|
||||||
crc = crc.value.to_bytes(2, byteorder='big') # Convert crc to 2 byte hex string
|
crc = crc.value.to_bytes(2, byteorder='big') # Convert crc to 2 byte hex string
|
||||||
buffer += crc # Append crc16 to buffer
|
buffer += crc # Append crc16 to buffer
|
||||||
|
|
||||||
|
@ -460,11 +477,13 @@ class RF():
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes_datac0 = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes)
|
nbytes_datac0 = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out,
|
||||||
|
self.datac0_buffer.buffer.ctypes)
|
||||||
self.datac0_buffer.pop(self.datac0_nin)
|
self.datac0_buffer.pop(self.datac0_nin)
|
||||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||||
if nbytes_datac0 == self.datac0_bytes_per_frame:
|
if nbytes_datac0 == self.datac0_bytes_per_frame:
|
||||||
self.modem_received_queue.put([self.datac0_bytes_out, self.datac0_freedv, self.datac0_bytes_per_frame])
|
self.modem_received_queue.put(
|
||||||
|
[self.datac0_bytes_out, self.datac0_freedv, self.datac0_bytes_per_frame])
|
||||||
# self.get_scatter(self.datac0_freedv)
|
# self.get_scatter(self.datac0_freedv)
|
||||||
self.calculate_snr(self.datac0_freedv)
|
self.calculate_snr(self.datac0_freedv)
|
||||||
|
|
||||||
|
@ -475,11 +494,13 @@ class RF():
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes_datac1 = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes)
|
nbytes_datac1 = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out,
|
||||||
|
self.datac1_buffer.buffer.ctypes)
|
||||||
self.datac1_buffer.pop(self.datac1_nin)
|
self.datac1_buffer.pop(self.datac1_nin)
|
||||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||||
if nbytes_datac1 == self.datac1_bytes_per_frame:
|
if nbytes_datac1 == self.datac1_bytes_per_frame:
|
||||||
self.modem_received_queue.put([self.datac1_bytes_out, self.datac1_freedv, self.datac1_bytes_per_frame])
|
self.modem_received_queue.put(
|
||||||
|
[self.datac1_bytes_out, self.datac1_freedv, self.datac1_bytes_per_frame])
|
||||||
# self.get_scatter(self.datac1_freedv)
|
# self.get_scatter(self.datac1_freedv)
|
||||||
self.calculate_snr(self.datac1_freedv)
|
self.calculate_snr(self.datac1_freedv)
|
||||||
|
|
||||||
|
@ -490,11 +511,13 @@ class RF():
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes_datac3 = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes)
|
nbytes_datac3 = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out,
|
||||||
|
self.datac3_buffer.buffer.ctypes)
|
||||||
self.datac3_buffer.pop(self.datac3_nin)
|
self.datac3_buffer.pop(self.datac3_nin)
|
||||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||||
if nbytes_datac3 == self.datac3_bytes_per_frame:
|
if nbytes_datac3 == self.datac3_bytes_per_frame:
|
||||||
self.modem_received_queue.put([self.datac3_bytes_out, self.datac3_freedv, self.datac3_bytes_per_frame])
|
self.modem_received_queue.put(
|
||||||
|
[self.datac3_bytes_out, self.datac3_freedv, self.datac3_bytes_per_frame])
|
||||||
# self.get_scatter(self.datac3_freedv)
|
# self.get_scatter(self.datac3_freedv)
|
||||||
self.calculate_snr(self.datac3_freedv)
|
self.calculate_snr(self.datac3_freedv)
|
||||||
|
|
||||||
|
@ -505,11 +528,13 @@ class RF():
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
while self.fsk_ldpc_buffer_0.nbuffer >= self.fsk_ldpc_nin_0:
|
while self.fsk_ldpc_buffer_0.nbuffer >= self.fsk_ldpc_nin_0:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes_fsk_ldpc_0 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_out_0, self.fsk_ldpc_buffer_0.buffer.ctypes)
|
nbytes_fsk_ldpc_0 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_out_0,
|
||||||
|
self.fsk_ldpc_buffer_0.buffer.ctypes)
|
||||||
self.fsk_ldpc_buffer_0.pop(self.fsk_ldpc_nin_0)
|
self.fsk_ldpc_buffer_0.pop(self.fsk_ldpc_nin_0)
|
||||||
self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0)
|
self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0)
|
||||||
if nbytes_fsk_ldpc_0 == self.fsk_ldpc_bytes_per_frame_0:
|
if nbytes_fsk_ldpc_0 == self.fsk_ldpc_bytes_per_frame_0:
|
||||||
self.modem_received_queue.put([self.fsk_ldpc_bytes_out_0, self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_per_frame_0])
|
self.modem_received_queue.put(
|
||||||
|
[self.fsk_ldpc_bytes_out_0, self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_per_frame_0])
|
||||||
# self.get_scatter(self.fsk_ldpc_freedv_0)
|
# self.get_scatter(self.fsk_ldpc_freedv_0)
|
||||||
self.calculate_snr(self.fsk_ldpc_freedv_0)
|
self.calculate_snr(self.fsk_ldpc_freedv_0)
|
||||||
|
|
||||||
|
@ -520,11 +545,13 @@ class RF():
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
while self.fsk_ldpc_buffer_1.nbuffer >= self.fsk_ldpc_nin_1:
|
while self.fsk_ldpc_buffer_1.nbuffer >= self.fsk_ldpc_nin_1:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes_fsk_ldpc_1 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_out_1, self.fsk_ldpc_buffer_1.buffer.ctypes)
|
nbytes_fsk_ldpc_1 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_out_1,
|
||||||
|
self.fsk_ldpc_buffer_1.buffer.ctypes)
|
||||||
self.fsk_ldpc_buffer_1.pop(self.fsk_ldpc_nin_1)
|
self.fsk_ldpc_buffer_1.pop(self.fsk_ldpc_nin_1)
|
||||||
self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1)
|
self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1)
|
||||||
if nbytes_fsk_ldpc_1 == self.fsk_ldpc_bytes_per_frame_1:
|
if nbytes_fsk_ldpc_1 == self.fsk_ldpc_bytes_per_frame_1:
|
||||||
self.modem_received_queue.put([self.fsk_ldpc_bytes_out_1, self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_per_frame_1])
|
self.modem_received_queue.put(
|
||||||
|
[self.fsk_ldpc_bytes_out_1, self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_per_frame_1])
|
||||||
# self.get_scatter(self.fsk_ldpc_freedv_1)
|
# self.get_scatter(self.fsk_ldpc_freedv_1)
|
||||||
self.calculate_snr(self.fsk_ldpc_freedv_1)
|
self.calculate_snr(self.fsk_ldpc_freedv_1)
|
||||||
|
|
||||||
|
@ -708,15 +735,18 @@ class RF():
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, n_frames_per_burst)
|
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, n_frames_per_burst)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, n_frames_per_burst)
|
codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, n_frames_per_burst)
|
||||||
|
|
||||||
|
|
||||||
def open_codec2_instance(mode):
|
def open_codec2_instance(mode):
|
||||||
""" Return a codec2 instance """
|
""" Return a codec2 instance """
|
||||||
if mode in ['FSK_LDPC_0', 200]:
|
if mode in ['FSK_LDPC_0', 200]:
|
||||||
return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), ctypes.c_void_p)
|
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)),
|
||||||
|
ctypes.c_void_p)
|
||||||
|
|
||||||
if mode in ['FSK_LDPC_1', 201]:
|
if mode in ['FSK_LDPC_1', 201]:
|
||||||
return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), ctypes.c_void_p)
|
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)),
|
||||||
|
ctypes.c_void_p)
|
||||||
|
|
||||||
return ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p)
|
return ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p)
|
||||||
|
|
||||||
|
|
39
tnc/sock.py
39
tnc/sock.py
|
@ -21,22 +21,19 @@ Created on Fri Dec 25 21:25:14 2020
|
||||||
"""
|
"""
|
||||||
import atexit
|
import atexit
|
||||||
import base64
|
import base64
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import queue
|
import queue
|
||||||
import socketserver
|
import socketserver
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import psutil
|
|
||||||
import structlog
|
import structlog
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
import audio
|
|
||||||
import data_handler
|
import data_handler
|
||||||
import helpers
|
import helpers
|
||||||
import log_handler
|
|
||||||
import static
|
import static
|
||||||
|
|
||||||
SOCKET_QUEUE = queue.Queue()
|
SOCKET_QUEUE = queue.Queue()
|
||||||
|
@ -138,7 +135,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
# finally delete our rx buffer to be ready for new commands
|
# finally delete our rx buffer to be ready for new commands
|
||||||
data = bytes()
|
data = bytes()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
structlog.get_logger("structlog").info("[SCK] Connection closed", ip=self.client_address[0], port=self.client_address[1], e=e)
|
structlog.get_logger("structlog").info("[SCK] Connection closed", ip=self.client_address[0],
|
||||||
|
port=self.client_address[1], e=e)
|
||||||
self.connection_alive = False
|
self.connection_alive = False
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
|
@ -147,7 +145,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
"""
|
"""
|
||||||
CONNECTED_CLIENTS.add(self.request)
|
CONNECTED_CLIENTS.add(self.request)
|
||||||
|
|
||||||
structlog.get_logger("structlog").debug("[SCK] Client connected", ip=self.client_address[0], port=self.client_address[1])
|
structlog.get_logger("structlog").debug("[SCK] Client connected", ip=self.client_address[0],
|
||||||
|
port=self.client_address[1])
|
||||||
self.connection_alive = True
|
self.connection_alive = True
|
||||||
|
|
||||||
self.sendThread = threading.Thread(target=self.send_to_client, args=[], daemon=True).start()
|
self.sendThread = threading.Thread(target=self.send_to_client, args=[], daemon=True).start()
|
||||||
|
@ -159,11 +158,14 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
""" """
|
""" """
|
||||||
structlog.get_logger("structlog").warning("[SCK] Closing client socket", ip=self.client_address[0], port=self.client_address[1])
|
structlog.get_logger("structlog").warning("[SCK] Closing client socket", ip=self.client_address[0],
|
||||||
|
port=self.client_address[1])
|
||||||
try:
|
try:
|
||||||
CONNECTED_CLIENTS.remove(self.request)
|
CONNECTED_CLIENTS.remove(self.request)
|
||||||
except:
|
except:
|
||||||
structlog.get_logger("structlog").warning("[SCK] client connection already removed from client list", client=self.request)
|
structlog.get_logger("structlog").warning("[SCK] client connection already removed from client list",
|
||||||
|
client=self.request)
|
||||||
|
|
||||||
|
|
||||||
def process_tnc_commands(data):
|
def process_tnc_commands(data):
|
||||||
"""
|
"""
|
||||||
|
@ -349,7 +351,9 @@ def process_tnc_commands(data):
|
||||||
# print(static.RX_BUFFER[i][4])
|
# print(static.RX_BUFFER[i][4])
|
||||||
# rawdata = json.loads(static.RX_BUFFER[i][4])
|
# rawdata = json.loads(static.RX_BUFFER[i][4])
|
||||||
base64_data = static.RX_BUFFER[i][4]
|
base64_data = static.RX_BUFFER[i][4]
|
||||||
output["data-array"].append({"uuid": static.RX_BUFFER[i][0],"timestamp": static.RX_BUFFER[i][1], "dxcallsign": str(static.RX_BUFFER[i][2], 'utf-8'), "dxgrid": str(static.RX_BUFFER[i][3], 'utf-8'), "data": base64_data})
|
output["data-array"].append({"uuid": static.RX_BUFFER[i][0], "timestamp": static.RX_BUFFER[i][1],
|
||||||
|
"dxcallsign": str(static.RX_BUFFER[i][2], 'utf-8'),
|
||||||
|
"dxgrid": str(static.RX_BUFFER[i][3], 'utf-8'), "data": base64_data})
|
||||||
|
|
||||||
jsondata = json.dumps(output)
|
jsondata = json.dumps(output)
|
||||||
# self.request.sendall(bytes(jsondata, encoding))
|
# self.request.sendall(bytes(jsondata, encoding))
|
||||||
|
@ -368,10 +372,11 @@ def process_tnc_commands(data):
|
||||||
command_response("del_rx_buffer", False)
|
command_response("del_rx_buffer", False)
|
||||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||||
|
|
||||||
# exception, if JSON cant be decoded
|
# exception, if JSON can't be decoded
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
structlog.get_logger("structlog").error("[TNC] JSON decoding error", e=e)
|
structlog.get_logger("structlog").error("[TNC] JSON decoding error", e=e)
|
||||||
|
|
||||||
|
|
||||||
def send_tnc_state():
|
def send_tnc_state():
|
||||||
"""
|
"""
|
||||||
send the tnc state to network
|
send the tnc state to network
|
||||||
|
@ -422,6 +427,7 @@ def send_tnc_state():
|
||||||
|
|
||||||
return json.dumps(output)
|
return json.dumps(output)
|
||||||
|
|
||||||
|
|
||||||
def process_daemon_commands(data):
|
def process_daemon_commands(data):
|
||||||
"""
|
"""
|
||||||
process daemon commands
|
process daemon commands
|
||||||
|
@ -441,13 +447,15 @@ def process_daemon_commands(data):
|
||||||
|
|
||||||
if bytes(callsign, 'utf-8') == b'':
|
if bytes(callsign, 'utf-8') == b'':
|
||||||
self.request.sendall(b'INVALID CALLSIGN')
|
self.request.sendall(b'INVALID CALLSIGN')
|
||||||
structlog.get_logger("structlog").warning("[DMN] SET MYCALL FAILED", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC)
|
structlog.get_logger("structlog").warning("[DMN] SET MYCALL FAILED", call=static.MYCALLSIGN,
|
||||||
|
crc=static.MYCALLSIGN_CRC)
|
||||||
else:
|
else:
|
||||||
static.MYCALLSIGN = bytes(callsign, 'utf-8')
|
static.MYCALLSIGN = bytes(callsign, 'utf-8')
|
||||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||||
|
|
||||||
command_response("mycallsign", True)
|
command_response("mycallsign", True)
|
||||||
structlog.get_logger("structlog").info("[DMN] SET MYCALL", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC)
|
structlog.get_logger("structlog").info("[DMN] SET MYCALL", call=static.MYCALLSIGN,
|
||||||
|
crc=static.MYCALLSIGN_CRC)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
command_response("mycallsign", False)
|
command_response("mycallsign", False)
|
||||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||||
|
@ -494,7 +502,8 @@ def process_daemon_commands(data):
|
||||||
|
|
||||||
# print some debugging parameters
|
# print some debugging parameters
|
||||||
for item in received_json["parameter"][0]:
|
for item in received_json["parameter"][0]:
|
||||||
structlog.get_logger("structlog").debug("[DMN] TNC Startup Config : " + item, value=received_json["parameter"][0][item])
|
structlog.get_logger("structlog").debug("[DMN] TNC Startup Config : " + item,
|
||||||
|
value=received_json["parameter"][0][item])
|
||||||
|
|
||||||
DAEMON_QUEUE.put(['STARTTNC',
|
DAEMON_QUEUE.put(['STARTTNC',
|
||||||
mycall,
|
mycall,
|
||||||
|
@ -572,6 +581,7 @@ def process_daemon_commands(data):
|
||||||
command_response("stop_tnc", False)
|
command_response("stop_tnc", False)
|
||||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||||
|
|
||||||
|
|
||||||
def send_daemon_state():
|
def send_daemon_state():
|
||||||
"""
|
"""
|
||||||
send the daemon state to network
|
send the daemon state to network
|
||||||
|
@ -604,6 +614,7 @@ def send_daemon_state():
|
||||||
structlog.get_logger("structlog").warning("[SCK] error", e=e)
|
structlog.get_logger("structlog").warning("[SCK] error", e=e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def command_response(command, status):
|
def command_response(command, status):
|
||||||
s_status = "OK" if status else "Failed"
|
s_status = "OK" if status else "Failed"
|
||||||
jsondata = {"command_response": command, "status": s_status}
|
jsondata = {"command_response": command, "status": s_status}
|
||||||
|
|
|
@ -111,7 +111,7 @@ RX_BURST_BUFFER = []
|
||||||
RX_FRAME_BUFFER = b''
|
RX_FRAME_BUFFER = b''
|
||||||
# RX_BUFFER_SIZE = 0
|
# RX_BUFFER_SIZE = 0
|
||||||
|
|
||||||
# ------- HEARD STATIOS BUFFER
|
# ------- HEARD STATIONS BUFFER
|
||||||
HEARD_STATIONS = []
|
HEARD_STATIONS = []
|
||||||
|
|
||||||
# ------- INFO MESSAGE BUFFER
|
# ------- INFO MESSAGE BUFFER
|
||||||
|
|
Loading…
Reference in a new issue