mirror of https://github.com/DJ2LS/FreeDATA.git
Merge pull request #940 from DJ2LS/develop
commit
c481972953
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.17.0-beta",
|
||||
"version": "0.17.1-beta",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
<script setup>
|
||||
|
||||
<script>
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
|
||||
// Set the active Pinia store
|
||||
setActivePinia(pinia);
|
||||
|
||||
// Export methods and computed properties for use in the template
|
||||
export default {
|
||||
computed: {
|
||||
settings() {
|
||||
return settings;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -17,10 +30,71 @@ setActivePinia(pinia);
|
|||
|
||||
|
||||
<div
|
||||
class="alert alert-info"
|
||||
class="alert alert-info d-none"
|
||||
role="alert"
|
||||
>
|
||||
<strong><i class="bi bi-info-circle me-1" /></strong>{{ $t('settings.exp.info') }}
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50 text-wrap">
|
||||
|
||||
{{ $t('settings.exp.enableringbuffer') }}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link p-0 ms-2"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="$t('settings.exp.enableringbuffer_help')"
|
||||
>
|
||||
<i class="bi bi-question-circle" />
|
||||
</button>
|
||||
</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
id="enable_ring_bufferSwitch"
|
||||
v-model="settings.remote.EXP.enable_ring_buffer"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
@change="onChange"
|
||||
>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="enable_ring_bufferSwitch"
|
||||
>{{ $t('settings.enable') }}</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50 text-wrap">
|
||||
|
||||
{{ $t('settings.exp.enablevhf') }}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link p-0 ms-2"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="$t('settings.exp.enablevhf_help')"
|
||||
>
|
||||
<i class="bi bi-question-circle" />
|
||||
</button>
|
||||
</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
id="enable_vhfSwitch"
|
||||
v-model="settings.remote.EXP.enable_vhf"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
@change="onChange"
|
||||
>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="enable_vhfSwitch"
|
||||
>{{ $t('settings.enable') }}</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -304,7 +304,11 @@
|
|||
},
|
||||
"exp": {
|
||||
"introduction": "Warnung: Diese Funktionen können unvollständig sein. Sie sind NUR für erfahrene Benutzer!",
|
||||
"info": "Info: Noch keine Funktionen zum Testen veröffentlicht"
|
||||
"info": "Info: Noch keine Funktionen zum Testen veröffentlicht",
|
||||
"enableringbuffer": "Ringpuffer einschalten",
|
||||
"enableringbuffer_help": "Aktivieren eines experimentellen Ringpuffers, der bei einer leistungsschwachen CPU effizienter sein kann",
|
||||
"enablevhf": "UKW-Modus aktivieren -- derzeit nur CQ --",
|
||||
"enablevhf_help": "Aktiviert den experimentellen UKW-Modus zum Testen"
|
||||
},
|
||||
"radio": {
|
||||
"introduction": "Einstellungen für die Transceiversteuerung, einschließlich der Auswahl der Rig-Control Methode und der Konfiguration spezifischer Einstellungen.",
|
||||
|
|
|
@ -304,7 +304,11 @@
|
|||
},
|
||||
"exp": {
|
||||
"introduction": "Warning: These features may be incomplete. They are for experienced users ONLY!",
|
||||
"info": "Info: No features publishes for testing, yet"
|
||||
"info": "Info: No features publishes for testing, yet",
|
||||
"enableringbuffer": "Enable ring buffer",
|
||||
"enableringbuffer_help": "Enable an experimental ring buffer which can be more efficient on low power CPU",
|
||||
"enablevhf": "Enable VHF modes -- only CQ for now --",
|
||||
"enablevhf_help": "Enable experimental VHF modes for testing and playing"
|
||||
},
|
||||
"radio": {
|
||||
"introduction": "Rig Control related settings, including selecting your rig control method and configuring specific settings.",
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
"ping": "Ping",
|
||||
"ping_help": "Manda una richiesta di ping a una stazione remota",
|
||||
"newmessage_help": "Invia un nuovo messaggio a una stazione remota",
|
||||
"startnewchat_help": "Inizia o continua una conversazione con un'altra stazione",
|
||||
"heardstations": "Stazioni ricevute",
|
||||
"time": "Tempo",
|
||||
"freq": "Freq",
|
||||
|
@ -110,6 +111,7 @@
|
|||
"sendingping": "Invio PING...",
|
||||
"callcq": "Chiamo CQ",
|
||||
"cqcqcq": "CQ CQ CQ",
|
||||
"cqcqcq_help": "Trasmetti un CQ a tutti",
|
||||
"togglebeacon": "attiva/disattiva beacon",
|
||||
"togglebeacon_help": "Attiva/disattiva la modalità beacon. Mentre invii un beacon, puoi ricevere una richiesta di ping e aprire un canale per scambio di dati. If viene aperto uno scambio di dati il beacon entra in pausa.",
|
||||
"enablebeacon": "Attiva il beacon",
|
||||
|
@ -142,6 +144,7 @@
|
|||
"furtheroptions": "Altre opzioni",
|
||||
"deletechat": "Cancella la chat",
|
||||
"startnewchat": "Inizia una nuova chat",
|
||||
"startnewchat2": "Inizia una nuova chat (prima digita il nominativo del destinatario)",
|
||||
"newchatline1": "Digita il callsign di destinazione",
|
||||
"newchatline2": "Digita il primo messaggio",
|
||||
"newchatline3": "Click su INIZIA UNA NUOVA CHAT",
|
||||
|
@ -150,7 +153,7 @@
|
|||
"audiotuning": "Regolazione audio",
|
||||
"audiotuninginfo": "Regola il livello audio. Valori in dB. Valore di default 0",
|
||||
"testframe": "Test frame",
|
||||
"testframetransmit": "Trasmetti ( 5s )",
|
||||
"testframetransmit": "Trasmetti (5s)",
|
||||
"audiotuningrxlevel": "Livello RX",
|
||||
"audiotuningtxlevel": "Livello TX",
|
||||
"audiotuningtransmitsine": "Trasmetti un segnale sinusoidale",
|
||||
|
@ -167,7 +170,7 @@
|
|||
"radiocontrol": "Controllo della radio",
|
||||
"radiocontrolinfo": "Questo è solo una configurazione rapida! Per altre opzioni vai a configurazione/controllo radio",
|
||||
"rigcontrolmethod": "Metodo di controllo della radio",
|
||||
"rigcontrolmethoddisabled": "Disabilitato ( nessun controllo della radio; usalo con VOX)",
|
||||
"rigcontrolmethoddisabled": "Disabilitato (nessun controllo della radio; usalo con VOX)",
|
||||
"rigcontrolmethodserialptt": "PTT seriale con DTR/RTS",
|
||||
"rigcontrolmethodrigctld": "Rigctld (Hamlib esterna)",
|
||||
"rigcontrolmethodrigctldbundle": "Rigctld (Hamlib interna)",
|
||||
|
@ -198,7 +201,11 @@
|
|||
"entermessage_placeholder": "Messaggio - Invia con [Invio]",
|
||||
"attempt": "tentativo",
|
||||
"utc": "UTC",
|
||||
"adif": "ADIF"
|
||||
"adif": "ADIF",
|
||||
"new": "nuovo",
|
||||
"selectChat": "Seleziona o inizia una chat",
|
||||
"noConversations": "Ancora nessuna conversazione",
|
||||
"loadingMessages": "Caricamento messaggi..."
|
||||
},
|
||||
"settings": {
|
||||
"enable": "Abilita",
|
||||
|
@ -297,7 +304,9 @@
|
|||
},
|
||||
"exp": {
|
||||
"introduction": "Attenzione: Queste funzioni possono essere incomplete. Sono SOLO per utenti esperti!",
|
||||
"info": "Info: Non sono ancora state rese disponibili funzioni per il test"
|
||||
"info": "Info: Non sono ancora state rese disponibili funzioni per il test",
|
||||
"enableringbuffer": "Attiva il ring buffer",
|
||||
"enableringbuffer_help": "Attiva un ring buffer sperimentale che può essere più efficiente su CPU di bassa potenza"
|
||||
},
|
||||
"radio": {
|
||||
"introduction": "Parametri per il controllo della radio, compreso il metodo di controllo e le configurazioni specifiche del metodo.",
|
||||
|
|
|
@ -87,6 +87,10 @@ const defaultConfig = {
|
|||
GUI: {
|
||||
auto_run_browser: true,
|
||||
},
|
||||
EXP: {
|
||||
enable_ring_buffer: false,
|
||||
enable_vhf: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class CircularBuffer:
|
|||
self.nbuffer = 0 # Number of samples stored.
|
||||
self.lock = threading.Lock()
|
||||
self.cond = threading.Condition(self.lock)
|
||||
log.debug("[C2 ] Creating audio buffer", size=size)
|
||||
log.debug("[BUF] Creating ring buffer", size=size)
|
||||
|
||||
def push(self, samples):
|
||||
"""Push samples onto the buffer.
|
||||
|
|
|
@ -39,6 +39,8 @@ class FREEDV_MODE(Enum):
|
|||
data_ofdm_500 = 21500
|
||||
data_ofdm_1700 = 211700
|
||||
data_ofdm_2438 = 2124381
|
||||
data_vhf_1 = 201
|
||||
|
||||
#data_qam_2438 = 2124382
|
||||
#qam16c2 = 22
|
||||
|
||||
|
@ -395,6 +397,17 @@ def open_instance(mode: int) -> ctypes.c_void_p:
|
|||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
|
||||
elif mode in [FREEDV_MODE.data_vhf_1.value]:
|
||||
fsk_custom = 9
|
||||
custom_params = fsk_configurations[mode]
|
||||
return ctypes.cast(
|
||||
api.freedv_open_advanced(
|
||||
fsk_custom,
|
||||
ctypes.byref(custom_params),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
else:
|
||||
if mode not in [data_custom]:
|
||||
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
||||
|
@ -468,6 +481,17 @@ class FREEDV_ADVANCED(ctypes.Structure):
|
|||
("config", ctypes.POINTER(OFDM_CONFIG))
|
||||
]
|
||||
|
||||
class FREEDV_ADVANCED_FSK(ctypes.Structure):
|
||||
"""Advanced structure for fsk and ofdm modes"""
|
||||
_fields_ = [
|
||||
("interleave_frames", ctypes.c_int),
|
||||
("M", ctypes.c_int),
|
||||
("Rs", ctypes.c_int),
|
||||
("Fs", ctypes.c_int),
|
||||
("first_tone", ctypes.c_int),
|
||||
("tone_spacing", ctypes.c_int),
|
||||
("codename", ctypes.c_char_p),
|
||||
]
|
||||
|
||||
api.freedv_open_advanced.argtypes = [ctypes.c_int, ctypes.POINTER(FREEDV_ADVANCED)]
|
||||
api.freedv_open_advanced.restype = ctypes.c_void_p
|
||||
|
@ -540,6 +564,47 @@ def create_tx_uw(nuwbits, uw_sequence):
|
|||
tx_uw_array[i] = uw_sequence[i]
|
||||
return tx_uw_array
|
||||
|
||||
|
||||
def create_default_fsk_config():
|
||||
return FREEDV_ADVANCED(
|
||||
interleave_frames = 0,
|
||||
M = 2,
|
||||
Rs = 100,
|
||||
Fs = 8000,
|
||||
first_tone = 1000,
|
||||
tone_spacing = 200,
|
||||
codename = "H_256_512_4".encode("utf-8"),
|
||||
config = None
|
||||
)
|
||||
|
||||
def get_centered_first_tone(config, center=1500):
|
||||
"""
|
||||
Calculate and return the first tone frequency so that the set of tones
|
||||
is centered at the given center frequency.
|
||||
|
||||
The tones are assumed to be spaced equally based on config.tone_spacing,
|
||||
and config.M is the total number of tones.
|
||||
|
||||
Args:
|
||||
config: A configuration object with the following attributes:
|
||||
- M (int): Total number of tones.
|
||||
- tone_spacing (float): Spacing between consecutive tones.
|
||||
center (float): Desired center frequency (default is 1500 Hz).
|
||||
|
||||
Returns:
|
||||
float: The computed value for the first tone.
|
||||
"""
|
||||
return int(center - ((config.M - 1) * config.tone_spacing) // 2)
|
||||
|
||||
|
||||
data_vhf_1_config = create_default_fsk_config()
|
||||
data_vhf_1_config.interleave_frames = 1
|
||||
data_vhf_1_config.M = 4
|
||||
data_vhf_1_config.Rs = 200
|
||||
data_vhf_1_config.tone_spacing = 400
|
||||
data_vhf_1_config.codename = "H_256_512_4".encode("utf-8")
|
||||
data_vhf_1_config.first_tone = get_centered_first_tone(data_vhf_1_config)
|
||||
|
||||
# ---------------- OFDM 500 Hz Bandwidth ---------------#
|
||||
|
||||
# DATAC13 # OFDM 200
|
||||
|
@ -693,6 +758,8 @@ ofdm_configurations = {
|
|||
FREEDV_MODE.data_ofdm_500.value: data_ofdm_500_config,
|
||||
FREEDV_MODE.data_ofdm_1700.value: data_ofdm_1700_config,
|
||||
FREEDV_MODE.data_ofdm_2438.value: data_ofdm_2438_config,
|
||||
#FREEDV_MODE.data_qam_2438.value: data_qam_2438_config
|
||||
|
||||
#FREEDV_MODE.data_qam_2438.value: data_qam_2438_config,
|
||||
}
|
||||
fsk_configurations = {
|
||||
FREEDV_MODE.data_vhf_1.value: data_vhf_1_config
|
||||
}
|
||||
|
|
|
@ -116,7 +116,13 @@ class TxCommand:
|
|||
Returns:
|
||||
FREEDV_MODE: The transmission mode.
|
||||
"""
|
||||
return FREEDV_MODE.signalling
|
||||
|
||||
if self.config['EXP'].get('enable_vhf'):
|
||||
mode = FREEDV_MODE.data_vhf_1
|
||||
else:
|
||||
mode = FREEDV_MODE.signalling
|
||||
|
||||
return mode
|
||||
|
||||
def make_modem_queue_item(self, mode, repeat, repeat_delay, frame):
|
||||
"""Creates a dictionary representing a modem queue item.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from command import TxCommand
|
||||
import codec2
|
||||
from codec2 import FREEDV_MODE
|
||||
|
||||
|
||||
class TestCommand(TxCommand):
|
||||
"""Command for transmitting test frames.
|
||||
|
@ -28,4 +30,9 @@ class TestCommand(TxCommand):
|
|||
Returns:
|
||||
codec2.FREEDV_MODE: The FreeDV mode for test frames.
|
||||
"""
|
||||
return codec2.FREEDV_MODE.data_ofdm_500
|
||||
if self.config['EXP'].get('enable_vhf'):
|
||||
mode = FREEDV_MODE.data_vhf_1
|
||||
else:
|
||||
mode = FREEDV_MODE.data_ofdm_500
|
||||
|
||||
return mode
|
||||
|
|
|
@ -66,3 +66,7 @@ adif_wavelog_api_key = API-KEY
|
|||
[GUI]
|
||||
auto_run_browser = True
|
||||
|
||||
[EXP]
|
||||
enable_ring_buffer = False
|
||||
enable_vhf = False
|
||||
|
||||
|
|
|
@ -79,6 +79,10 @@ class CONFIG:
|
|||
|
||||
'GUI': {
|
||||
'auto_run_browser': bool,
|
||||
},
|
||||
'EXP': {
|
||||
'enable_ring_buffer': bool,
|
||||
'enable_vhf': bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Module for saving some constants
|
||||
CONFIG_ENV_VAR = 'FREEDATA_CONFIG'
|
||||
DEFAULT_CONFIG_FILE = 'config.ini'
|
||||
MODEM_VERSION = "0.17.0-beta"
|
||||
MODEM_VERSION = "0.17.1-beta"
|
||||
API_VERSION = 3
|
||||
LICENSE = 'GPL3.0'
|
||||
DOCUMENTATION_URL = 'https://wiki.freedata.app'
|
||||
|
|
|
@ -8,6 +8,8 @@ import itertools
|
|||
from audio_buffer import CircularBuffer
|
||||
|
||||
|
||||
from codec2 import (FREEDV_MODE)
|
||||
|
||||
TESTMODE = False
|
||||
|
||||
class Demodulator():
|
||||
|
@ -56,8 +58,14 @@ class Demodulator():
|
|||
self.init_codec2()
|
||||
|
||||
# enable decoding of signalling modes
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = True
|
||||
if self.config['EXP'].get('enable_vhf'):
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.data_vhf_1.value]["decode"] = True
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = True
|
||||
else:
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = True
|
||||
|
||||
|
||||
|
||||
def init_codec2(self):
|
||||
|
@ -72,7 +80,6 @@ class Demodulator():
|
|||
"""
|
||||
|
||||
# create codec2 instance
|
||||
#c2instance = ctypes.cast(
|
||||
c2instance = codec2.open_instance(mode)
|
||||
|
||||
# get bytes per frame
|
||||
|
@ -81,13 +88,18 @@ class Demodulator():
|
|||
)
|
||||
# create byte out buffer
|
||||
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||
|
||||
# set initial frames per burst
|
||||
codec2.api.freedv_set_frames_per_burst(c2instance, 1)
|
||||
|
||||
# init audio buffer
|
||||
#audio_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
audio_buffer = CircularBuffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
if self.config['EXP'].get('enable_ring_buffer'):
|
||||
self.log.debug("[MDM] [buffer]", enable_ring_buffer=True)
|
||||
audio_buffer = CircularBuffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
else:
|
||||
self.log.debug("[MDM] [buffer]", enable_ring_buffer=False)
|
||||
audio_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -326,6 +338,11 @@ class Demodulator():
|
|||
|
||||
# signalling is always true
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||
|
||||
if self.config['EXP'].get('enable_vhf'):
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.data_vhf_1.value]["decode"] = True
|
||||
|
||||
|
||||
# we only need to decode signalling ack as ISS or within P2P Connection
|
||||
if is_arq_irs and not is_p2p_connection:
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = False
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import ctypes
|
||||
import codec2
|
||||
import structlog
|
||||
from codec2 import FREEDV_MODE
|
||||
from codec2 import FREEDV_ADVANCED_FSK
|
||||
|
||||
|
||||
class Modulator:
|
||||
|
@ -48,6 +50,8 @@ class Modulator:
|
|||
self.data_ofdm_500_tx = codec2.open_instance(codec2.FREEDV_MODE.data_ofdm_500.value)
|
||||
self.data_ofdm_1700_tx = codec2.open_instance(codec2.FREEDV_MODE.data_ofdm_1700.value)
|
||||
self.data_ofdm_2438_tx = codec2.open_instance(codec2.FREEDV_MODE.data_ofdm_2438.value)
|
||||
self.data_vhf_1 = codec2.open_instance(codec2.FREEDV_MODE.data_vhf_1.value)
|
||||
|
||||
#self.freedv_qam16c2_tx = codec2.open_instance(codec2.FREEDV_MODE.qam16c2.value)
|
||||
#self.data_qam_2438_tx = codec2.open_instance(codec2.FREEDV_MODE.data_qam_2438.value)
|
||||
|
||||
|
@ -207,6 +211,7 @@ class Modulator:
|
|||
codec2.FREEDV_MODE.data_ofdm_2438: self.data_ofdm_2438_tx,
|
||||
#codec2.FREEDV_MODE.qam16c2: self.freedv_qam16c2_tx,
|
||||
#codec2.FREEDV_MODE.data_qam_2438: self.freedv_data_qam_2438_tx,
|
||||
codec2.FREEDV_MODE.data_vhf_1: self.data_vhf_1
|
||||
}
|
||||
if mode in mode_transition:
|
||||
freedv = mode_transition[mode]
|
||||
|
@ -234,9 +239,11 @@ class Modulator:
|
|||
|
||||
# Create modulation for all frames in the list
|
||||
for frame in frames:
|
||||
txbuffer = self.transmit_add_preamble(txbuffer, freedv)
|
||||
if not self.config['EXP'].get('enable_vhf'):
|
||||
txbuffer = self.transmit_add_preamble(txbuffer, freedv)
|
||||
txbuffer = self.transmit_create_frame(txbuffer, freedv, frame)
|
||||
txbuffer = self.transmit_add_postamble(txbuffer, freedv)
|
||||
if not self.config['EXP'].get('enable_vhf'):
|
||||
txbuffer = self.transmit_add_postamble(txbuffer, freedv)
|
||||
|
||||
# Add delay to end of frames
|
||||
txbuffer = self.transmit_add_silence(txbuffer, repeat_delay)
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import structlog
|
||||
import threading
|
||||
import websocket
|
||||
import time
|
||||
|
||||
class TCICtrl:
|
||||
def __init__(self, audio_rx_q, hostname='127.0.0.1', port=50001):
|
||||
# websocket.enableTrace(True)
|
||||
self.log = structlog.get_logger("TCI")
|
||||
|
||||
self.audio_received_queue = audio_rx_q
|
||||
|
||||
self.hostname = str(hostname)
|
||||
self.port = str(port)
|
||||
|
||||
self.ws = ''
|
||||
|
||||
tci_thread = threading.Thread(
|
||||
target=self.connect,
|
||||
name="TCI THREAD",
|
||||
daemon=True,
|
||||
)
|
||||
tci_thread.start()
|
||||
|
||||
# flag if we're receiving a tx_chrono
|
||||
self.tx_chrono = False
|
||||
|
||||
# audio related parameters, will be updated by tx chrono
|
||||
self.sample_rate = None
|
||||
self.format = None
|
||||
self.codec = None
|
||||
self.audio_length = None
|
||||
self.crc = None
|
||||
self.channel = None
|
||||
|
||||
self.frequency = None
|
||||
self.bandwidth = None
|
||||
self.mode = None
|
||||
self.alc = None
|
||||
self.meter = None
|
||||
self.level = None
|
||||
self.ptt = None
|
||||
|
||||
def connect(self):
|
||||
self.log.info(
|
||||
"[TCI] Starting TCI thread!", ip=self.hostname, port=self.port
|
||||
)
|
||||
self.ws = websocket.WebSocketApp(
|
||||
f"ws://{self.hostname}:{self.port}",
|
||||
on_open=self.on_open,
|
||||
on_message=self.on_message,
|
||||
on_error=self.on_error,
|
||||
on_close=self.on_close,
|
||||
)
|
||||
|
||||
self.ws.run_forever(reconnect=5) # Set dispatcher to automatic reconnection, 5 second reconnect delay if con>
|
||||
# rel.signal(2, rel.abort) # Keyboard Interrupt
|
||||
# rel.dispatch()
|
||||
|
||||
def on_message(self, ws, message):
|
||||
|
||||
# ready message
|
||||
# we need to wait until radio is ready before we can push commands
|
||||
if message == "ready;":
|
||||
self.ws.send('audio_samplerate:8000;')
|
||||
self.ws.send('audio_stream_channels:1;')
|
||||
self.ws.send('audio_stream_sample_type:int16;')
|
||||
self.ws.send('audio_stream_samples:1200;')
|
||||
self.ws.send('audio_start:0;')
|
||||
|
||||
# tx chrono frame
|
||||
if len(message) in {64}:
|
||||
receiver = message[:4]
|
||||
sample_rate = int.from_bytes(message[4:8], "little")
|
||||
format = int.from_bytes(message[8:12], "little")
|
||||
codec = int.from_bytes(message[12:16], "little")
|
||||
crc = int.from_bytes(message[16:20], "little")
|
||||
audio_length = int.from_bytes(message[20:24], "little")
|
||||
type = int.from_bytes(message[24:28], "little")
|
||||
channel = int.from_bytes(message[28:32], "little")
|
||||
reserved1 = int.from_bytes(message[32:36], "little")
|
||||
reserved2 = int.from_bytes(message[36:40], "little")
|
||||
reserved3 = int.from_bytes(message[40:44], "little")
|
||||
reserved4 = int.from_bytes(message[44:48], "little")
|
||||
reserved5 = int.from_bytes(message[48:52], "little")
|
||||
reserved6 = int.from_bytes(message[52:56], "little")
|
||||
reserved7 = int.from_bytes(message[56:60], "little")
|
||||
reserved8 = int.from_bytes(message[60:64], "little")
|
||||
if type == 3:
|
||||
self.tx_chrono = True
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.format = format
|
||||
self.codec = codec
|
||||
self.audio_length = audio_length
|
||||
self.channel = channel
|
||||
self.crc = crc
|
||||
|
||||
# audio frame
|
||||
if len(message) in {576, 2464, 4160}:
|
||||
# audio received
|
||||
receiver = message[:4]
|
||||
sample_rate = int.from_bytes(message[4:8], "little")
|
||||
format = int.from_bytes(message[8:12], "little")
|
||||
codec = int.from_bytes(message[12:16], "little")
|
||||
crc = int.from_bytes(message[16:20], "little")
|
||||
audio_length = int.from_bytes(message[20:24], "little")
|
||||
type = int.from_bytes(message[24:28], "little")
|
||||
channel = int.from_bytes(message[28:32], "little")
|
||||
reserved1 = int.from_bytes(message[32:36], "little")
|
||||
reserved2 = int.from_bytes(message[36:40], "little")
|
||||
reserved3 = int.from_bytes(message[40:44], "little")
|
||||
reserved4 = int.from_bytes(message[44:48], "little")
|
||||
reserved5 = int.from_bytes(message[48:52], "little")
|
||||
reserved6 = int.from_bytes(message[52:56], "little")
|
||||
reserved7 = int.from_bytes(message[56:60], "little")
|
||||
reserved8 = int.from_bytes(message[60:64], "little")
|
||||
audio_data = message[64:]
|
||||
self.audio_received_queue.put(audio_data)
|
||||
|
||||
|
||||
if len(message)< 64:
|
||||
# find frequency
|
||||
if bytes(message, "utf-8").startswith(b"vfo:0,0,"):
|
||||
splitted_message = message.split("vfo:0,0,")
|
||||
self.frequency = splitted_message[1][:-1]
|
||||
|
||||
# find mode
|
||||
if bytes(message, "utf-8").startswith(b"modulation:0,"):
|
||||
splitted_message = message.split("modulation:0,")
|
||||
self.mode = splitted_message[1][:-1]
|
||||
|
||||
# find ptt
|
||||
#if bytes(message, "utf-8").startswith(b"trx:0,"):
|
||||
# splitted_message = message.split("trx:0,")
|
||||
# self.ptt = splitted_message[1][:-1]
|
||||
|
||||
# find bandwidth
|
||||
#if message.startswith("rx_filter_band:0,"):
|
||||
# splitted_message = message.split("rx_filter_band:0,")
|
||||
# bandwidths = splitted_message[1]
|
||||
# splitted_bandwidths = bandwidths.split(",")
|
||||
# lower_bandwidth = int(splitted_bandwidths[0])
|
||||
# upper_bandwidth = int(splitted_bandwidths[1][:-1])
|
||||
# self.bandwidth = upper_bandwidth - lower_bandwidth
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def on_error(self, ws, error):
|
||||
self.log.error(
|
||||
"[TCI] Error FreeDATA to TCI rig!", ip=self.hostname, port=self.port, e=error
|
||||
)
|
||||
|
||||
def on_close(self, ws, close_status_code, close_msg):
|
||||
self.log.warning(
|
||||
"[TCI] Closed FreeDATA to TCI connection!", ip=self.hostname, port=self.port, statu=close_status_code,
|
||||
msg=close_msg
|
||||
)
|
||||
|
||||
def on_open(self, ws):
|
||||
self.ws = ws
|
||||
self.log.info(
|
||||
"[TCI] Connected FreeDATA to TCI rig!", ip=self.hostname, port=self.port
|
||||
)
|
||||
|
||||
self.log.info(
|
||||
"[TCI] Init...", ip=self.hostname, port=self.port
|
||||
)
|
||||
|
||||
def push_audio(self, data_out):
|
||||
#print(data_out)
|
||||
|
||||
"""
|
||||
# audio[:4] = receiver.to_bytes(4,byteorder='little', signed=False)
|
||||
audio[4:8] = sample_rate.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[8:12] = format.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[12:16] = codec.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[16:20] = crc.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[20:24] = audio_length.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[24:28] = int(2).to_bytes(4, byteorder='little', signed=True)
|
||||
audio[28:32] = channel.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[32:36] = reserved1.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[36:40] = reserved2.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[40:44] = reserved3.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[44:48] = reserved4.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[48:52] = reserved5.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[52:56] = reserved6.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[56:60] = reserved7.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[60:64] = reserved8.to_bytes(4, byteorder='little', signed=False)
|
||||
"""
|
||||
|
||||
while not self.tx_chrono:
|
||||
time.sleep(0.01)
|
||||
|
||||
#print(len(data_out))
|
||||
#print(self.sample_rate)
|
||||
#print(self.audio_length)
|
||||
#print(self.channel)
|
||||
#print(self.crc)
|
||||
#print(self.codec)
|
||||
#print(self.tx_chrono)
|
||||
|
||||
if self.tx_chrono:
|
||||
#print("#############")
|
||||
#print(len(data_out))
|
||||
#print(len(bytes(data_out)))
|
||||
#print("-------------")
|
||||
audio = bytearray(4096 + 64)
|
||||
|
||||
audio[64:64 + len(bytes(data_out))] = bytes(data_out)
|
||||
audio[4:8] = self.sample_rate.to_bytes(4, byteorder='little', signed=False)
|
||||
# audio[8:12] = format.to_bytes(4,byteorder='little', signed=False)
|
||||
audio[12:16] = self.codec.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[16:20] = self.crc.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[20:24] = self.audio_length.to_bytes(4, byteorder='little', signed=False)
|
||||
audio[24:28] = int(2).to_bytes(4, byteorder='little', signed=False)
|
||||
audio[28:32] = self.channel.to_bytes(4, byteorder='little', signed=False)
|
||||
# audio[32:36] = reserved1.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[36:40] = reserved2.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[40:44] = reserved3.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[44:48] = reserved4.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[48:52] = reserved5.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[52:56] = reserved6.to_bytes(4,byteorder='little', signed=False)
|
||||
# audio[56:60] = reserved7.to_bytes(4,byteorder='little', signed=False)
|
||||
|
||||
self.ws.send(audio, websocket.ABNF.OPCODE_BINARY)
|
||||
|
||||
def set_ptt(self, state):
|
||||
if state:
|
||||
self.ws.send('trx:0,true,tci;')
|
||||
else:
|
||||
|
||||
self.ws.send('trx:0,false;')
|
||||
self.tx_chrono = False
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
self.ws.send('VFO:0,0;')
|
||||
return self.frequency
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
self.ws.send('MODULATION:0;')
|
||||
return self.mode
|
||||
|
||||
def get_level(self):
|
||||
""" """
|
||||
return self.level
|
||||
|
||||
def get_alc(self):
|
||||
""" """
|
||||
return self.alc
|
||||
|
||||
def get_meter(self):
|
||||
""" """
|
||||
return self.meter
|
||||
|
||||
def get_bandwidth(self):
|
||||
""" """
|
||||
return self.bandwidth
|
||||
|
||||
def get_strength(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def set_bandwidth(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.ws.send(f'MODULATION:0,{str(mode)};')
|
||||
return None
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
"""
|
||||
|
||||
Args:
|
||||
frequency:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.ws.send(f'VFO:0,0,{str(frequency)};')
|
||||
return None
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
self.ws.send('trx:0;')
|
||||
return self.ptt
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
return
|
||||
|
||||
def wait_until_transmitted(self, txbuffer_out):
|
||||
duration = len(txbuffer_out) / 8000
|
||||
timestamp_to_sleep = time.time() + duration
|
||||
self.log.debug("[MDM] TCI calculated duration", duration=duration)
|
||||
tci_timeout_reached = False
|
||||
while not tci_timeout_reached:
|
||||
if self.radiocontrol in ["tci"]:
|
||||
if time.time() < timestamp_to_sleep:
|
||||
tci_timeout_reached = False
|
||||
else:
|
||||
tci_timeout_reached = True
|
||||
threading.Event().wait(0.01)
|
||||
# if we're transmitting FreeDATA signals, reset channel busy state
|
Loading…
Reference in New Issue