Merge pull request #963 from DJ2LS/develop

v0.17.2-beta
pull/978/head
DJ2LS 2025-04-25 09:51:49 +02:00 committed by GitHub
commit 5d6e2ea1bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 417 additions and 17 deletions

View File

@ -1,6 +1,6 @@
{
"name": "FreeDATA",
"version": "0.17.1-beta",
"version": "0.17.2-beta",
"description": "FreeDATA Client application for connecting to FreeDATA server",
"private": true,
"scripts": {

View File

@ -228,6 +228,9 @@
<option value="rigctld_bundle">
Rigctld (internal Hamlib)
</option>
<option value="flrig">
flrig
</option>
</select>
</div>
<!-- Shown when rigctld_bundle is selected -->
@ -414,6 +417,7 @@ function getRigControlStatus() {
case 'serial_ptt':
case 'rigctld':
case 'rigctld_bundle':
case 'flrig':
return state.radio_status;
default:
console.error('Unknown radio control mode ' + settings.remote.RADIO.control);

View File

@ -0,0 +1,64 @@
<template>
<!-- Flrig host -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50 text-wrap">
{{ $t('settings.radio.flrighost') }}
<button
type="button"
class="btn btn-link p-0 ms-2"
data-bs-toggle="tooltip"
:title="$t('settings.radio.flrighost_help')"
>
<i class="bi bi-question-circle" />
</button>
</label>
<input
id="flrighost"
v-model="settings.remote.FLRIG.ip"
type="text"
class="form-control"
placeholder="127.0.0.1"
aria-label="flrig host"
@change="onChange"
>
</div>
<!-- Flrig port -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50 text-wrap">
{{ $t('settings.radio.flrigport') }}
<button
type="button"
class="btn btn-link p-0 ms-2"
data-bs-toggle="tooltip"
:title="$t('settings.radio.flrigport_help')"
>
<i class="bi bi-question-circle" />
</button>
</label>
<input
id="flrigport"
v-model.number="settings.remote.FLRIG.port"
type="number"
class="form-control"
placeholder="12345"
aria-label="flrig port"
@change="onChange"
>
</div>
</template>
<script setup>
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import { useSerialStore } from "../store/serialStore";
import { setActivePinia } from "pinia";
import pinia from "../store/index";
setActivePinia(pinia);
const serialStore = useSerialStore(pinia);
</script>

View File

@ -238,6 +238,66 @@ function reloadModem(){
</div>
</div>
<!-- RX Audio Auto Adjust -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50 text-wrap">
{{ $t('settings.modem.enablerxautoadjust') }}
<button
type="button"
class="btn btn-link p-0 ms-2"
data-bs-toggle="tooltip"
:title="$t('settings.modem.enablerxautoadjust_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="enableRXAutoAudioSwitch"
v-model="settings.remote.AUDIO.rx_auto_audio_level"
class="form-check-input"
type="checkbox"
@change="onChange"
>
<label
class="form-check-label"
for="enableRXAutoAudioSwitch"
>{{ $t('settings.enable') }}</label>
</div>
</label>
</div>
<!-- TX Audio Auto Adjust -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50 text-wrap">
{{ $t('settings.modem.enabletxautoadjust') }}
<button
type="button"
class="btn btn-link p-0 ms-2"
data-bs-toggle="tooltip"
:title="$t('settings.modem.enabletxautoadjust_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="enableTXAutoAudioSwitch"
v-model="settings.remote.AUDIO.tx_auto_audio_level"
class="form-check-input"
type="checkbox"
@change="onChange"
>
<label
class="form-check-label"
for="enableTXAutoAudioSwitch"
>{{ $t('settings.enable') }}</label>
</div>
</label>
</div>
<!-- TX Delay -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50 text-wrap">

View File

@ -46,6 +46,9 @@
<option value="rigctld_bundle">
Rigctld (internal Hamlib)
</option>
<option value="flrig">
flrig
</option>
</select>
</div>
@ -84,6 +87,19 @@
>
{{ $t('settings.radio.tabserial') }}
</button>
<button
id="nav-flrig-tab"
class="nav-link"
data-bs-toggle="tab"
data-bs-target="#nav-flrig"
type="button"
role="tab"
aria-controls="nav-flrig"
aria-selected="false"
>
{{ $t('settings.radio.tabflrig') }}
</button>
</div>
</nav>
@ -113,6 +129,19 @@
>
<settings_serial_ptt />
</div>
<!-- Flrig settings -->
<div
id="nav-flrig"
class="tab-pane fade"
role="tabpanel"
aria-labelledby="nav-flrig-tab"
tabindex="2"
>
<settings_flrig />
</div>
</div>
<hr class="m-2">
@ -122,11 +151,13 @@
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import settings_hamlib from "./settings_hamlib.vue";
import settings_serial_ptt from "./settings_serial_ptt.vue";
import settings_flrig from "./settings_flrig.vue";
export default {
components: {
settings_hamlib,
settings_serial_ptt
settings_serial_ptt,
settings_flrig
},
computed: {
settings() {

View File

@ -80,10 +80,10 @@
"radiocontrol": "Transceiversteuerung/Status",
"statistics": "Statistik",
"other": "Diverses",
"uploadpreset": "Preset Hochladen",
"uploadpreset_help": "Lade eine Preset Datei hoch.",
"downloadpreset": "Preset Herunterladen",
"downloadpreset_help": "Lade eine Preset-Date herunter um sie zu speichern und zu teilen",
"uploadpreset": "Einstell. Hochladen",
"uploadpreset_help": "Lade eine Datei mit GUI-Einstellungen hoch",
"downloadpreset": "Eintell. Herunterladen",
"downloadpreset_help": "Lade die GUI-Einstellungen herunter um sie zu speichern und zu teilen",
"components": {
"tune": "Tune",
"stop_help": "Sitzung abbrechen und Aussendung beenden",
@ -292,7 +292,11 @@
"txdelay": "TX-Verzögerung in ms",
"txdelay_help": "Sendeverzögerung, in Millisekuinden",
"maximumbandwidth": "Maximale Bandbreite",
"maximumbandwidth_help": "Wähle die maximale Bandbreite, die das Modem nutzen soll"
"maximumbandwidth_help": "Wähle die maximale Bandbreite, die das Modem nutzen soll",
"enabletxautoadjust": "Auto TX Audio Pegel",
"enablerxautoadjust": "Auto RX Audio Pegel",
"enabletxautoadjust_help": "Automatische Einstellung des Sendepegels auf den höchstmöglichen Wert" ,
"enablerxautoadjust_help": "Automatische Einstellung des Empfangspegels auf den höchstmöglichen Wert"
},
"web": {
"introduction": "Explorer-bezogene Einstellungen, einschließlich der Aktivierung der Explorer-Veröffentlichung und der Veröffentlichung von Explorer-Statistiken.",

View File

@ -292,7 +292,12 @@
"txdelay": "TX delay in ms",
"txdelay_help": "Delay before transmitting, in milliseconds",
"maximumbandwidth": "Maximum used bandwidth",
"maximumbandwidth_help": "Select the maximum bandwidth the modem will use"
"maximumbandwidth_help": "Select the maximum bandwidth the modem will use",
"enabletxautoadjust": "Auto TX audio level",
"enablerxautoadjust": "Auto RX audio level",
"enabletxautoadjust_help": "Set TX audio level automatically to highest possible value" ,
"enablerxautoadjust_help": "Set RX audio level automatically to highest possible value"
},
"web": {
"introduction": "Explorer related settings, including enabling Explorer publishing and Explorer stats publishing.",
@ -317,6 +322,7 @@
"rigcontroltype_help": "Choose how the software controls your radio",
"tabhamlib": "Hamlib",
"tabserial": "Serial",
"tabflrig": "flrig",
"serialpttcomport": "PTT Com port",
"serialpttcomport_help": "Select the COM port connected to your radio for PTT control",
"serialpttcustomcomport": "PTT Custom COM port",
@ -360,7 +366,11 @@
"hamlibrigctldcommand_placeholder": "Auto-populated from above settings",
"hamlibrigctldcustomarguments": " Rigctld custom arguments",
"hamlibrigctldcustomarguments_help": "Additional arguments for rigctld (usually not needed)",
"hamlibrigctldcustomarguments_placeholder": "Optional custom arguments"
"hamlibrigctldcustomarguments_placeholder": "Optional custom arguments",
"flrigport": "Flrig port",
"flrigport_help": "Set the port where the flrig server is listening ( Default: 12345)",
"flrighost": "Flrig host",
"flrighost_help": "Set the host adress of the server, mostly localhost or 127.0.0.1"
}
}
}

View File

@ -22,6 +22,8 @@ const defaultConfig = {
output_device: "",
rx_audio_level: 0,
tx_audio_level: 0,
rx_auto_audio_level: true,
tx_auto_audio_level: false
},
MODEM: {
tx_delay: 0,
@ -54,6 +56,10 @@ const defaultConfig = {
arguments: "",
enable_vfo: false,
},
FLRIG: {
ip: "127.0.0.1",
port: 12345,
},
STATION: {
enable_explorer: false,
enable_stats: false,

View File

@ -249,12 +249,12 @@ def normalize_audio(datalist: np.ndarray) -> np.ndarray:
:rtype: np.ndarray
"""
if not isinstance(datalist, np.ndarray):
print("[MDM] Invalid datalist type. Expected np.ndarray.")
#print("[MDM] Invalid datalist type. Expected np.ndarray.")
return datalist
# Ensure datalist is not empty
if datalist.size == 0:
print("[MDM] Datalist is empty. Returning unmodified.")
#print("[MDM] Datalist is empty. Returning unmodified.")
return datalist
# Find the maximum absolute value in the data
@ -262,7 +262,7 @@ def normalize_audio(datalist: np.ndarray) -> np.ndarray:
# If max_value is 0, return the datalist (avoid division by zero)
if max_value == 0:
print("[MDM] Max value is zero. Cannot normalize. Returning unmodified.")
#print("[MDM] Max value is zero. Cannot normalize. Returning unmodified.")
return datalist
# Define the target max value as 95% of the maximum for np.int16
@ -280,7 +280,7 @@ def normalize_audio(datalist: np.ndarray) -> np.ndarray:
# Debug information: normalization factor, loudest value before, and after normalization
loudest_before = max_value
loudest_after = np.max(np.abs(normalized_data))
print(f"[AUDIO] Normalization factor: {normalization_factor:.6f}, Loudest before: {loudest_before}, Loudest after: {loudest_after}")
# print(f"[AUDIO] Normalization factor: {normalization_factor:.6f}, Loudest before: {loudest_before}, Loudest after: {loudest_after}")
return normalized_data

View File

@ -18,6 +18,8 @@ input_device = 5a1c
output_device = bd6c
rx_audio_level = 0
tx_audio_level = 0
rx_auto_audio_level = True
tx_auto_audio_level = False
[RIGCTLD]
ip = 127.0.0.1
@ -27,6 +29,10 @@ command =
arguments =
enable_vfo = False
[FLRIG]
ip = 127.0.0.1
port = 12345
[RADIO]
control = disabled
model_id = 1001

View File

@ -31,6 +31,8 @@ class CONFIG:
'output_device': str,
'rx_audio_level': int,
'tx_audio_level': int,
'rx_auto_audio_level': bool,
'tx_auto_audio_level': bool,
},
'RADIO': {
'control': str,
@ -54,6 +56,10 @@ class CONFIG:
'arguments': str,
'enable_vfo': bool,
},
'FLRIG': {
'ip': str,
'port': int,
},
'MODEM': {
'enable_morse_identifier': bool,
'maximum_bandwidth': int,

View File

@ -1,7 +1,7 @@
# Module for saving some constants
CONFIG_ENV_VAR = 'FREEDATA_CONFIG'
DEFAULT_CONFIG_FILE = 'config.ini'
MODEM_VERSION = "0.17.1-beta"
MODEM_VERSION = "0.17.2-beta"
API_VERSION = 3
LICENSE = 'GPL3.0'
DOCUMENTATION_URL = 'https://wiki.freedata.app'

View File

@ -0,0 +1,203 @@
import xmlrpc.client
import threading
import time
import logging
class radio:
def __init__(self, config, state_manager, host='127.0.0.1', port=12345, poll_interval=1.0):
self.logger = logging.getLogger(__name__)
self.config = config
self.state_manager = state_manager
self.host = self.config["FLRIG"]["ip"]
self.port = self.config["FLRIG"]["port"]
self.poll_interval = poll_interval
self.server = None
self.connected = False
self.parameters = {
'frequency': '---',
'mode': '---',
'alc': '---',
'strength': '---',
'bandwidth': '---',
'rf': '---',
'ptt': False,
'tuner': False,
'swr': '---'
}
self._stop_event = threading.Event()
self._thread = threading.Thread(target=self._poll_loop, daemon=True)
self._thread.start()
def connect(self, **kwargs):
try:
self.server = xmlrpc.client.ServerProxy(f"http://{self.host}:{self.port}")
self.connected = True
self.logger.info("Connected to FLRig")
self.state_manager.set_radio("radio_status", True)
return True
except Exception as e:
self.logger.error(f"FLRig connection failed: {e}")
self.connected = False
self.server = None
self.state_manager.set_radio("radio_status", False)
return False
def disconnect(self, **kwargs):
self.logger.info("Disconnected from FLRig")
self.connected = False
self.server = None
self.state_manager.set_radio("radio_status", False)
return True
def _poll_loop(self):
while not self._stop_event.is_set():
if not self.connected:
self.connect()
if self.connected:
try:
self.get_frequency()
self.get_mode()
self.get_rf()
self.get_ptt()
self.get_swr()
self.get_frequency()
self.get_mode()
self.get_level()
except Exception as e:
self.logger.warning(f"Polling error: {e}")
self.connected = False
self.server = None
time.sleep(self.poll_interval)
def get_frequency(self):
self.parameters['frequency'] = self.server.rig.get_vfo()
return self.parameters['frequency']
def get_rf(self):
current_power_level = self.server.rig.get_power()
max_power_level = self.server.rig.get_maxpwr()
power_percentage = (int(current_power_level) / int(max_power_level)) * 100
self.parameters['rf'] = round(power_percentage, 0)
return self.parameters['rf']
def set_frequency(self, frequency):
self.parameters['frequency'] = frequency
if self.connected:
try:
self.server.main.set_frequency(float(frequency))
except Exception as e:
self.logger.error(f"Set frequency failed: {e}")
self.connected = False
def get_mode(self):
self.parameters['mode'] = self.server.rig.get_mode()
return self.parameters['mode']
def set_mode(self, mode):
self.parameters['mode'] = mode
if self.connected:
try:
self.server.rig.set_mode(mode)
except Exception as e:
self.logger.error(f"Set mode failed: {e}")
self.connected = False
def get_level(self):
self.parameters['strength'] = self.server.rig.get_smeter()
def get_alc(self):
return None
def get_meter(self):
return None
def get_bandwidth(self):
return self.parameters['bandwidth']
def set_bandwidth(self, bandwidth):
if self.connected:
try:
self.server.rig.set_bandwidth(int(bandwidth))
except Exception as e:
self.logger.error(f"Set bandwidth failed: {e}")
self.connected = False
def set_rf_level(self, rf):
if self.connected:
try:
# Get max power from rig
max_power = self.server.rig.get_maxpwr()
# Calculate absolute power in watts (rounded to int)
power_watts = int((float(rf) / 100) * float(max_power))
# Set power level in watts
self.server.rig.set_power(power_watts)
except Exception as e:
self.logger.error(f"Set RF level failed: {e}")
self.connected = False
def get_strength(self):
return self.parameters['strength']
def get_tuner(self):
return self.parameters['tuner']
def set_tuner(self, state):
self.parameters['tuner'] = state
return None
def get_swr(self):
self.parameters['swr'] = self.server.rig.get_swrmeter()
return self.parameters['swr']
def get_ptt(self):
self.parameters['ptt'] = self.server.rig.get_ptt()
return self.parameters['ptt']
def set_ptt(self, state):
self.parameters['ptt'] = state
if self.connected:
try:
if state:
result = self.server.rig.set_ptt(1)
else:
result = self.server.rig.set_ptt(0)
except Exception as e:
self.logger.error(f"Set PTT failed: {e}")
return state
def set_tuner(self, state):
self.parameters['ptt'] = state
if self.connected:
try:
self.server.rig.tune(state)
except Exception as e:
self.logger.error(f"Set Tune failed: {e}")
return state
def get_status(self):
return self.connected
def get_parameters(self):
return self.parameters
def close_rig(self):
self.logger.info("Closing FLRig interface")
self._stop_event.set()
self._thread.join()
def stop_service(self):
self.close_rig()

View File

@ -302,7 +302,9 @@ class RF:
# Re-sample back up to 48k (resampler works on np.int16)
x = np.frombuffer(txbuffer, dtype=np.int16)
x = audio.normalize_audio(x)
if self.config['AUDIO'].get('tx_auto_audio_level'):
x = audio.normalize_audio(x)
x = audio.set_audio_volume(x, self.tx_audio_level)
txbuffer_out = self.resampler.resample8_to_48(x)
# transmit audio
@ -410,6 +412,8 @@ class RF:
try:
audio_48k = np.frombuffer(indata, dtype=np.int16)
audio_8k = self.resampler.resample48_to_8(audio_48k)
if self.config['AUDIO'].get('rx_auto_audio_level'):
audio_8k = audio.normalize_audio(audio_8k)
audio_8k_level_adjusted = audio.set_audio_volume(audio_8k, self.rx_audio_level)

View File

@ -1,4 +1,5 @@
import rigctld
import flrig
import rigdummy
import serial_ptt
import threading
@ -25,7 +26,8 @@ class RadioManager:
self.radio = rigctld.radio(self.config, self.state_manager, hostname=self.rigctld_ip,port=self.rigctld_port)
elif self.radiocontrol == "serial_ptt":
self.radio = serial_ptt.radio(self.config, self.state_manager)
elif self.radiocontrol == "flrig":
self.radio = flrig.radio(self.config, self.state_manager)
else:
self.radio = rigdummy.radio()

View File

@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setup(
name='freedata',
version='0.16.10',
version='0.17.2',
packages=find_packages(where='.'),
package_dir={'': '.'},
install_requires=required,