Merge branch 'develop' into dependabot/npm_and_yarn/gui/develop/vite-plugin-electron-0.28.0

This commit is contained in:
DJ2LS 2024-01-15 16:56:41 +01:00 committed by GitHub
commit 9b407d5e96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 1654 additions and 1255 deletions

View file

@ -11,17 +11,17 @@ jobs:
os: [ubuntu-20.04, macos-latest, windows-latest]
include:
- os: ubuntu-20.04
zip_name: ubuntu_modem
zip_name: freedata-server_ubuntu
generator: Unix Makefiles
modem_binary_name: freedata-server
- os: macos-latest
zip_name: macos_modem
zip_name: freedata-server_macos
generator: Unix Makefiles
modem_binary_name: freedata-server
- os: windows-latest
zip_name: windows_modem
zip_name: freedata-server_windows
generator: Visual Studio 16 2019
modem_binary_name: freedata-server.exe
@ -74,25 +74,25 @@ jobs:
- name: Build binaries
working-directory: modem
run: |
python3 -m nuitka --enable-plugin=numpy --remove-output --assume-yes-for-downloads --standalone server.py
python3 -m nuitka --remove-output --assume-yes-for-downloads --follow-imports --include-data-dir=lib=lib --include-data-files=lib/codec2/*=lib/codec2/ --include-data-files=config.ini.example=config.ini --standalone server.py --output-filename=freedata-server
- name: Download Portaudio binaries Linux macOS
if: ${{!startsWith(matrix.os, 'windows')}}
working-directory: modem
run: |
if ! test -d "server.dist/modem/_sounddevice_data"; then
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
fi
#- name: Download Portaudio binaries Linux macOS
# if: ${{!startsWith(matrix.os, 'windows')}}
# working-directory: modem
# run: |
# if ! test -d "server.dist/modem/_sounddevice_data"; then
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
# fi
- name: Download Portaudio binaries Windows
if: ${{startsWith(matrix.os, 'windows')}}
working-directory: modem
run: |
if(Test-Path -Path "server.dist/modem/_sounddevice_data"){
echo "sounddevice folder already exists"
} else {
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
}
#- name: Download Portaudio binaries Windows
# if: ${{startsWith(matrix.os, 'windows')}}
# working-directory: modem
# run: |
# if(Test-Path -Path "server.dist/modem/_sounddevice_data"){
# echo "sounddevice folder already exists"
# } else {
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
# }
- name: LIST ALL FILES
run: ls -R
@ -113,6 +113,15 @@ jobs:
path: .
# exclusions: '*.git* /*node_modules/* .editorconfig'
- name: LIST ALL FILES
run: ls -R
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: '${{ matrix.zip_name }}'
path: ./modem/server.dist/${{ matrix.zip_name }}.zip
- name: Release Modem
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/v')

View file

@ -4,8 +4,6 @@
"port": "5000",
"spectrum": "waterfall",
"wf_theme": 2,
"theme": "default_light",
"high_graphics": true,
"update_channel": "alpha",
"enable_sys_notification": false
}

View file

@ -57,6 +57,7 @@
>
<div
class="progress rounded-0 rounded-bottom"
hidden
:style="{ height: '10px' }"
v-bind:class="{
'bg-danger': message.status == 'failed',

View file

@ -50,7 +50,7 @@ const chatModuleMessage=ref(null);
function transmitNewMessage(){
chat.inputText = chat.inputText.trim();
if (chat.inputText.length==0)
if (chat.inputText.length==0 && chat.inputFileName == "-")
return;
if (chat.selectedCallsign.startsWith("BC-")) {

View file

@ -8,7 +8,7 @@ import "../../node_modules/gridstack/dist/gridstack.min.css";
import { GridStack } from "gridstack";
import { useStateStore } from "../store/stateStore.js";
const state = useStateStore(pinia);
import { setModemFrequency } from "../js/api";
import { setRadioParameters } from "../js/api";
import { saveLocalSettingsToConfig, settingsStore } from "../store/settingsStore";
import active_heard_stations from "./grid/grid_active_heard_stations.vue";
@ -25,14 +25,20 @@ import grid_button from "./grid/button.vue";
import grid_ptt from "./grid/grid_ptt.vue";
import grid_mycall from "./grid/grid_mycall.vue";
import grid_stop from "./grid/grid_stop.vue";
import grid_tune from "./grid/grid_tune.vue";
import grid_CQ_btn from "./grid/grid_CQ.vue";
import grid_ping from "./grid/grid_ping.vue";
import grid_freq from "./grid/grid_frequency.vue";
import grid_beacon from "./grid/grid_beacon.vue";
import grid_mycall_small from "./grid/grid_mycall small.vue";
import grid_scatter from "./grid/grid_scatter.vue";
import { stateDispatcher } from "../js/eventHandler";
import { Scatter } from "vue-chartjs";
let count = ref(0);
let grid = null; // DO NOT use ref(null) as proxies GS will break all logic when comparing structures... see https://github.com/gridstack/gridstack.js/issues/2115
let items = ref([]);
let gridEnabledLocal = ref(true);
class gridWidget {
//Contains the vue component
component2;
@ -46,32 +52,46 @@ class gridWidget {
autoPlace;
//Category to place widget in widget picker
category;
constructor(component, size, text, quickfill, autoPlace, category) {
//Unique ID for widget
id;
constructor(component, size, text, quickfill, autoPlace, category, id) {
this.component2 = component;
this.size = size;
this.text = text;
this.quickFill = quickfill;
this.autoPlace = autoPlace;
this.category = category;
this.id = id;
}
}
//Array of grid widgets, do not change array order as it'll affect saved configs
const gridWidgets = [
new gridWidget(
active_heard_stations,
{ x: 0, y: 0, w: 16, h: 40 },
"Detailed heard stations list",
new gridWidget(
grid_activities,
{ x: 0, y: 53, w: 6, h: 55 },
"Activities list",
true,
true,
"Activity",
8,
),
new gridWidget(
active_heard_stations,
{ x: 0, y: 13, w: 16, h: 40 },
"Heard stations list (detailed)",
true,
true,
"Activity",
0,
),
new gridWidget(
active_stats,
{ x: 16, y: 16, w: 8, h: 80 },
{ x: 16, y: 16, w: 8, h: 72 },
"Stats (waterfall, etc)",
true,
true,
"Stats",
1,
),
new gridWidget(
active_audio_level,
@ -80,94 +100,7 @@ const gridWidgets = [
false,
true,
"Audio",
),
new gridWidget(
active_rig_control,
{ x: 6, y: 40, w: 9, h: 15 },
"Rig control main",
false,
true,
"Rig",
),
new gridWidget(
active_broadcasts,
{ x: 6, y: 70, w: 6, h: 15 },
"Broadcasts main (horizontal)",
false,
true,
"Broadcasts",
),
new gridWidget(
mini_heard_stations,
{ x: 1, y: 1, w: 6, h: 54 },
"Mini heard stations list",
false,
true,
"Activity",
),
new gridWidget(
s_meter,
{ x: 16, y: 0, w: 4, h: 8 },
"S-Meter",
true,
true,
"Rig",
),
new gridWidget(
dbfs_meter,
{ x: 20, y: 0, w: 4, h: 8 },
"Dbfs Meter",
true,
true,
"Audio",
),
new gridWidget(
grid_activities,
{ x: 0, y: 40, w: 6, h: 55 },
"Activities list",
true,
true,
"Activity",
),
new gridWidget(
active_broadcasts_vert,
{ x: 9, y: 55, w: 10, h: 40 },
"Broadcasts main (vertical)",
true,
true,
"Broadcasts",
),
new gridWidget(
grid_ptt,
{ x: 17, y: 8, w: 5, h: 12 },
"Tx/PTT indicator",
true,
true,
"Rig",
),
new gridWidget(
grid_mycall,
{ x: 8, y: 40, w: 5, h: 15 },
"My callsign widget",
true,
true,
"Other",
),
new gridWidget(
grid_CQ_btn,
{ x: 3, y: 27, w: 2, h: 8 },
"CQ Button",
false,
true,
"Broadcasts",
),
new gridWidget(
grid_ping,
{ x: 3, y: 27, w: 4, h: 9 },
"Ping Widget",
false,
true,
"Broadcasts",
2,
),
new gridWidget(
grid_freq,
@ -176,26 +109,178 @@ const gridWidgets = [
true,
true,
"Rig",
14,
),
new gridWidget(
grid_stop,
{ x: 8, y: 40, w: 5, h: 15 },
"Stop Widget",
new gridWidget(
active_rig_control,
{ x: 6, y: 40, w: 9, h: 15 },
"Rig control main",
false,
true,
"Rig",
3,
),
new gridWidget(
grid_beacon,
{ x: 3, y: 27, w: 3, h: 8 },
"Beacon button",
false,
true,
"Broadcasts",
16,
),
new gridWidget(
active_broadcasts,
{ x: 6, y: 70, w: 6, h: 15 },
"Broadcasts main (horizontal)",
false,
true,
"Broadcasts",
4,
),
new gridWidget(
mini_heard_stations,
{ x: 1, y: 1, w: 6, h: 54 },
"Heard stations list (small)",
false,
true,
"Activity",
5,
),
new gridWidget(
s_meter,
{ x: 16, y: 0, w: 4, h: 8 },
"S-Meter",
true,
true,
"Rig",
6,
),
new gridWidget(
dbfs_meter,
{ x: 20, y: 0, w: 4, h: 8 },
"Dbfs meter",
true,
true,
"Audio",
7,
),
new gridWidget(
active_broadcasts_vert,
{ x: 6, y: 53, w: 10, h: 35 },
"Broadcasts main (vertical)",
true,
true,
"Broadcasts",
9,
),
new gridWidget(
grid_ptt,
{ x: 2, y: 0, w: 5, h: 13 },
"Tx/PTT indicator",
true,
true,
"Rig",
10,
),
new gridWidget(
grid_mycall,
{ x: 7, y: 0, w: 9, h: 13 },
"My callsign widget",
true,
true,
"Other",
11,
),
new gridWidget(
grid_mycall_small,
{ x: 8, y: 40, w: 4, h: 8 },
"My callsign widget (small)",
false,
true,
"Other",
17,
),
new gridWidget(
grid_CQ_btn,
{ x: 3, y: 27, w: 2, h: 8 },
"CQ button",
false,
true,
"Broadcasts",
12,
),
new gridWidget(
grid_ping,
{ x: 3, y: 27, w: 4, h: 9 },
"Ping widget",
false,
true,
"Broadcasts",
13,
),
new gridWidget(
grid_stop,
{ x: 0, y: 0, w: 2, h: 13 },
"Stop widget",
true,
true,
"Other",
15,
),
new gridWidget(
grid_tune,
{ x: 16, y: 8, w: 2, h: 8 },
"Tune widget",
true,
true,
"Audio",
18,
),
new gridWidget(
grid_scatter,
{ x: 0, y: 114, w: 6, h: 30 },
"Scatter graph",
false,
true,
"Stats",
19,
),
//New new widget ID should be 20
];
function updateFrequencyAndApply(frequency) {
state.new_frequency = frequency;
setModemFrequency(state.new_frequency);
set_radio_parameters();
}
function set_hamlib_frequency_manually() {
setModemFrequency(state.new_frequency);
function set_radio_parameters(){
setRadioParameters(state.new_frequency, state.mode, state.rf_level);
}
function savePreset()
{
settingsStore.local.grid_preset=settingsStore.local.grid_layout;
console.log("Saved grid preset")
}
function loadPreset()
{
clearAllItems();
settingsStore.local.grid_layout=settingsStore.local.grid_preset;
restoreGridLayoutFromConfig();
console.log("Restored grid preset")
}
onMounted(() => {
grid = GridStack.init({
// DO NOT use grid.value = GridStack.init(), see above
@ -290,13 +375,17 @@ function onChange(event, changeItems) {
function restoreGridLayoutFromConfig(){
//Try to load grid from saved config
//On mounted seems to be called multiple times; so check to make sure items is empty first
//array format: 0 = x, 1 = y, 2 = w, 3 = h, 4 = gridwidget index
//array format: 0 = x, 1 = y, 2 = w, 3 = h, 4 = gridwidget ID
if (items.value.length == 0){
let savedGrid = JSON.parse(settingsStore.local.grid_layout);
if (savedGrid.length > 0 ) console.info("Restoring " + savedGrid.length + " widget(s) from config");
for (let i=0; i < savedGrid.length;i++ ){
//Find widget by ID
var widgetIndex = gridWidgets.findIndex((gw) => gw.id == savedGrid[i][4])
//Refs are passed, so grab original settings for restoration
let tempGW = gridWidgets[parseInt(savedGrid[i][4])];
//let tempGW = gridWidgets[parseInt(savedGrid[i][4])];
let tempGW = gridWidgets[widgetIndex];
let backupGWsize = tempGW.size;
tempGW.autoPlace=false;
tempGW.size={x:savedGrid[i][0], y:savedGrid[i][1], w:savedGrid[i][2], h:savedGrid[i][3]}
@ -312,7 +401,11 @@ function saveGridLayout()
let cfg = [];
for (let i=0; items.value.length > i; i++) {
var widget = gridWidgets.findIndex((gw) => gw.component2.__name == items.value[i].component2.__name)
cfg[i] = [items.value[i].x, items.value[i].y, items.value[i].w,items.value[i].h, widget ];
//Get the widget's id to store in config
var widgetid = gridWidgets[widget].id;
//Debug code to return index of widget based on id
//console.log(widgetid + "-" + widget);
cfg[i] = [items.value[i].x, items.value[i].y, items.value[i].w,items.value[i].h, widgetid ];
}
settingsStore.local.grid_layout=JSON.stringify(cfg);
saveLocalSettingsToConfig();
@ -329,7 +422,7 @@ function addNewWidget2(componentToAdd :gridWidget,saveToConfig :boolean) {
if (saveToConfig)
saveGridLayout();
});
}
function remove(widget) {
@ -339,7 +432,13 @@ function remove(widget) {
grid.removeWidget(selector, false);
saveGridLayout();
}
function disableGrid() {
gridEnabledLocal.value = !gridEnabledLocal.value
if (gridEnabledLocal.value)
grid.enable();
else
grid.disable();
}
function clearAllItems() {
grid.removeAll(false);
count.value = 0;
@ -388,6 +487,7 @@ function quickfill() {
<button
@click="remove(w)"
class="btn-close grid-stack-floaty-btn"
:class="gridEnabledLocal === true ? 'visible':'invisible'"
></button>
<component :is="w.component2" />
</div>
@ -405,7 +505,14 @@ function quickfill() {
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasGridItemsLabel">
Manage grid widgets
Manage grid widgets &nbsp;<button
class="btn btn-sm btn-outline-info"
type="button"
@click="disableGrid"
title="Lock/unloack changes to grid"
>
<i class="bi" :class="gridEnabledLocal == true ? 'bi-unlock-fill' : 'bi-lock-fill'"></i>
</button>&nbsp;
</h5>
<button
@ -580,9 +687,28 @@ function quickfill() {
class="btn btn-sm btn-outline-warning"
type="button"
@click="clearAllItems"
title="Clear all items from the grid"
>
Clear grid
</button>
<hr/>
<button
class="btn btn-sm btn-outline-dark"
type="button"
@click="loadPreset"
title="Restore your saved grid preset (clears current grid)"
>
Restore preset
</button>&nbsp;
<button
class="btn btn-sm btn-outline-dark"
type="button"
@click="savePreset"
title="Save current grid layout as a preset that can be restored using restore preset button"
>
Save preset
</button>
</div>
</div>
@ -597,7 +723,7 @@ function quickfill() {
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
@ -627,7 +753,7 @@ function quickfill() {
<button
class="btn btn-sm btn-outline-success"
type="button"
@click="set_hamlib_frequency_manually"
@click="updateFrequencyAndApply(state.new_frequency)"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"

View file

@ -6,7 +6,10 @@ function emitClick() {
}
</script>
<template>
<button class="btn btn-small btn-outline-secondary" v-on:click="emitClick">
<button
class="btn btn-small btn-outline-secondary mb-1"
v-on:click="emitClick"
>
{{ btnText }}
</button>
</template>

View file

@ -8,28 +8,9 @@ const state = useStateStore(pinia);
</script>
<template>
<div class="card w-100 h-100">
<div class="card-header p-0">
<div class="dropdown">
<button
class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
></button>
<i class="bi bi-volume-up" style="font-size: 1rem"></i>&nbsp;
<strong>Audio</strong>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#audioModal"
href="#"
>Tune</a
>
</li>
</ul>
</div>
<div class="card-header p-0 mb-1">
<i class="bi bi-volume-up" style="font-size: 1rem"></i>&nbsp;
<strong>Audio</strong>
</div>
<div class="card-body pt-0 pb-0">
<div class="container-wide">

View file

@ -1,18 +1,14 @@
<script setup lang="ts">
import { setActivePinia } from "pinia";
import pinia from "../../store/index";
import { setModemRigMode, setModemRigPowerLvl } from "../../js/api";
import { setRadioParameters } from "../../js/api";
setActivePinia(pinia);
import { useStateStore } from "../../store/stateStore.js";
const state = useStateStore(pinia);
function set_hamlib_mode() {
setModemRigMode(state.mode);
}
function set_hamlib_rf_level() {
setModemRigPowerLvl(state.rf_level);
function set_radio_parameters() {
setRadioParameters(state.frequency, state.mode, state.rf_level);
}
</script>
@ -51,7 +47,7 @@ function set_hamlib_rf_level() {
<select
class="form-control"
v-model="state.mode"
@click="set_hamlib_mode()"
@click="set_radio_parameters()"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"
@ -73,7 +69,7 @@ function set_hamlib_rf_level() {
<select
class="form-control"
v-model="state.rf_level"
@click="set_hamlib_rf_level()"
@click="set_radio_parameters()"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"

View file

@ -332,10 +332,7 @@ onMounted(() => {
role="stats_tabpanel"
aria-labelledby="list-waterfall-list"
>
<canvas
v-bind:id="localSpectrumID"
:class="settings.local.high_graphics == true ? 'force-gpu' : ''"
></canvas>
<canvas v-bind:id="localSpectrumID" class="force-gpu"></canvas>
</div>
<div
class="tab-pane fade h-100 w-100"

View file

@ -0,0 +1,36 @@
<script setup lang="ts">
import { setActivePinia } from "pinia";
import pinia from "../../store/index";
setActivePinia(pinia);
import { setModemBeacon } from "../../js/api.js";
import { useStateStore } from "../../store/stateStore.js";
const state = useStateStore(pinia);
function startStopBeacon() {
if (state.beacon_state === true) {
setModemBeacon(false);
} else {
setModemBeacon(true);
}
}
</script>
<template>
<div class="fill h-100" style="width: calc(100% - 24px)">
<a
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
@click="startStopBeacon"
title="Enable/disable periodic beacons"
>Beacon&nbsp;
<span
class=""
role="status"
v-bind:class="{
'spinner-grow spinner-grow-sm': state.beacon_state === true,
disabled: state.beacon_state === false,
}"
>
</span>
</a>
</div>
</template>

View file

@ -0,0 +1,30 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { setActivePinia } from "pinia";
import { setConfig } from "../../js/api";
import pinia from "../../store/index";
setActivePinia(pinia);
import { settingsStore } from "../../store/settingsStore.js";
</script>
<template>
<div class="w-100">
<div class="input-group input-group-sm" style="width: calc(100% - 24px)">
<input
type="text"
class="form-control"
disabled
style="min-width: 3em; background-color: transparent"
v-model="settingsStore.remote.STATION.mycall"
/>
<span class="input-group-text">-</span>
<input
type="text"
class="form-control"
disabled
style="min-width: 2em; max-width: 2.5em; background-color: transparent"
v-model="settingsStore.remote.STATION.myssid"
/>
</div>
</div>
</template>

View file

@ -6,19 +6,15 @@ import pinia from "../../store/index";
setActivePinia(pinia);
import { settingsStore } from "../../store/settingsStore.js";
function updateMyCall() {
setConfig();
}
</script>
<template>
<div
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
>
<h1>
<h2>
{{ settingsStore.remote.STATION.mycall }}-{{
settingsStore.remote.STATION.myssid
}}
</h1>
</h2>
</div>
</template>

View file

@ -0,0 +1,91 @@
<script setup lang="ts">
// @ts-nocheck
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
import { computed, onMounted } from "vue";
import { setActivePinia } from "pinia";
import pinia from "../../store/index";
setActivePinia(pinia);
import { useStateStore } from "../../store/stateStore.js";
const state = useStateStore(pinia);
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Scatter } from "vue-chartjs";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
);
// https://www.chartjs.org/docs/latest/samples/line/segments.html
const scatterChartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "linear",
position: "bottom",
grid: {
display: true,
lineWidth: 1, // Set the line width for x-axis grid lines
},
ticks: {
display: true,
},
},
y: {
type: "linear",
position: "left",
grid: {
display: true,
lineWidth: 1, // Set the line width for y-axis grid lines
},
ticks: {
display: true,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
},
},
};
const scatterChartData = computed(() => ({
datasets: [
{
type: "scatter",
fill: true,
data: state.scatter,
label: "Scatter",
tension: 0.1,
borderColor: "rgb(0, 255, 0)",
},
],
}));
</script>
<template>
<div class="w-100 h-100">
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
</div>
<!--278px-->
</template>

View file

@ -1,7 +1,5 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { setActivePinia } from "pinia";
import { setConfig } from "../../js/api";
import pinia from "../../store/index";
setActivePinia(pinia);
@ -13,19 +11,15 @@ function stopAllTransmissions() {
}
</script>
<template>
<div
class="d-flex justify-content-center align-items-center object-fill rounded w-100 h-100"
<a
class="btn btn-outline-danger d-flex border justify-content-center align-items-center object-fill rounded w-100 h-100"
id="stop_transmission_connection"
data-bs-toggle="tooltip"
data-bs-trigger="hover"
data-bs-html="false"
@click="stopAllTransmissions()"
title="Abort session and stop transmissions"
>
<button
class="btn border btn-outline-danger list-group-item w-100 h-100"
id="stop_transmission_connection"
data-bs-toggle="tooltip"
data-bs-trigger="hover"
data-bs-html="false"
@click="stopAllTransmissions()"
title="Abort session and stop transmissions"
>
<i class="bi bi-sign-stop-fill h1"></i>
</button>
</div>
<i class="bi bi-sign-stop-fill h1"></i>
</a>
</template>

View file

@ -0,0 +1,18 @@
<script setup lang="ts">
import { setActivePinia } from "pinia";
import pinia from "../../store/index";
setActivePinia(pinia);
</script>
<template>
<div class="fill h-100" style="width: calc(100% - 24px)">
<a
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
data-bs-trigger="hover"
data-bs-html="false"
data-bs-toggle="modal"
data-bs-target="#audioModal"
title="Tune"
>Tune</a
>
</div>
</template>

View file

@ -18,7 +18,7 @@ import main_active_audio_level from "./main_active_audio_level.vue";
import chat from "./chat.vue";
import infoScreen from "./infoScreen.vue";
import main_modem_healthcheck from "./main_modem_healthcheck.vue";
import Dynamic_components2 from "./dynamic_components2.vue";
import Dynamic_components from "./dynamic_components.vue";
import { stopTransmission } from "../js/api";
@ -321,7 +321,7 @@ function stopAllTransmissions() {
role="tabpanel"
aria-labelledby="list-grid-list"
>
<Dynamic_components2 />
<Dynamic_components />
</div>
<div

View file

@ -6,23 +6,15 @@ setActivePinia(pinia);
import { useStateStore } from "../store/stateStore.js";
const state = useStateStore(pinia);
import { setModemRigMode, setModemRigPowerLvl } from "../js/api";
import { setRadioParameters } from "../js/api";
function updateFrequencyAndApply(frequency) {
//state.new_frequency = frequency;
//set_frequency(state.new_frequency);
state.new_frequency = frequency;
set_radio_parameters();
}
function set_hamlib_frequency_manually() {
//set_frequency(state.new_frequency);
}
function set_hamlib_mode() {
setModemRigMode(state.mode);
}
function set_hamlib_rf_level() {
setModemRigPowerLvl(state.rf_level);
function set_radio_parameters() {
setRadioParameters(state.new_frequency, state.mode, state.rf_level);
}
</script>
@ -102,7 +94,7 @@ function set_hamlib_rf_level() {
<button
class="btn btn-sm btn-outline-success"
type="button"
@click="set_hamlib_frequency_manually"
@click="updateFrequencyAndApply(state.new_frequency)"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"
@ -215,7 +207,7 @@ function set_hamlib_rf_level() {
<select
class="form-control"
v-model="state.mode"
@click="set_hamlib_mode()"
@click="set_radio_parameters()"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"
@ -237,7 +229,7 @@ function set_hamlib_rf_level() {
<select
class="form-control"
v-model="state.rf_level"
@click="set_hamlib_rf_level()"
@click="set_radio_parameters()"
v-bind:class="{
disabled: state.hamlib_status === 'disconnected',
}"

View file

@ -368,7 +368,7 @@ export default {
width: 100%;
height: 200px;
"
:class="settings.local.high_graphics == true ? 'force-gpu' : ''"
class="force-gpu'"
></canvas>
</div>
<div

View file

@ -215,10 +215,7 @@ const state = useStateStore(pinia);
</div>
<div class="col-lg-4">
<div style="margin-right: 2px">
<div
class="progress w-100 rounded-0 rounded-top"
style="height: 20px; min-width: 200px"
>
<div class="progress w-100" style="height: 20px; min-width: 200px">
<div
class="progress-bar progress-bar-striped bg-primary force-gpu"
id="transmission_progress"
@ -231,11 +228,12 @@ const state = useStateStore(pinia);
<p
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
>
{{ state.arq_seconds_until_finish }}s left
Message Progress
</p>
</div>
<div
hidden
class="progress mb-0 rounded-0 rounded-bottom"
style="height: 10px"
>

View file

@ -8,7 +8,7 @@ setActivePinia(pinia);
import { useChatStore } from "../store/chatStore.js";
const chat = useChatStore(pinia);
import { settingsStore as settings } from "../store/settingsStore.js";
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import { sendModemTestFrame } from "../js/api";
@ -19,16 +19,6 @@ import {
import main_startup_check from "./main_startup_check.vue";
function set_tx_audio_level() {
saveSettingsToFile();
setTxAudioLevel(settings.tx_audio_level);
}
function set_rx_audio_level() {
saveSettingsToFile();
setRxAudioLevel(settings.rx_audio_level);
}
function deleteChat() {
//console.log(chat.selectedCallsign)
deleteChatByCallsign(chat.selectedCallsign);
@ -1186,6 +1176,10 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
></button>
</div>
<div class="modal-body">
<div class="alert alert-info" role="alert">
Adjust audio levels. Value in dB. Default is <strong>0</strong>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text">Test-Frame</span>
<button
@ -1199,7 +1193,9 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text">RX Level</span>
<span class="input-group-text">{{ settings.rx_audio_level }}</span>
<span class="input-group-text">{{
settings.remote.AUDIO.rx_audio_level
}}</span>
<span class="input-group-text w-75">
<input
type="range"
@ -1208,13 +1204,15 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
max="20"
step="1"
id="audioLevelRX"
@click="set_rx_audio_level()"
v-model="settings.rx_audio_level"
@change="onChange"
v-model.number="settings.remote.AUDIO.rx_audio_level"
/></span>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text">TX Level</span>
<span class="input-group-text">{{ settings.tx_audio_level }}</span>
<span class="input-group-text">{{
settings.remote.AUDIO.tx_audio_level
}}</span>
<span class="input-group-text w-75">
<input
type="range"
@ -1223,8 +1221,8 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
max="20"
step="1"
id="audioLevelTX"
@click="set_tx_audio_level()"
v-model="settings.tx_audio_level"
@change="onChange"
v-model.number="settings.remote.AUDIO.tx_audio_level"
/></span>
</div>
</div>

View file

@ -3,7 +3,6 @@ import settings_station from "./settings_station.vue";
import settings_gui from "./settings_gui.vue";
import settings_chat from "./settings_chat.vue";
import settings_rigcontrol from "./settings_rigcontrol.vue";
import settings_hamlib from "./settings_hamlib.vue";
import settings_modem from "./settings_modem.vue";
import settings_web from "./settings_web.vue";
import settings_exp from "./settings_exp.vue";
@ -76,20 +75,7 @@ import settings_exp from "./settings_exp.vue";
Rig Control
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="hamlib-tab"
data-bs-toggle="tab"
data-bs-target="#hamlib"
type="button"
role="tab"
aria-controls="profile"
aria-selected="false"
>
Hamlib
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
@ -134,7 +120,10 @@ import settings_exp from "./settings_exp.vue";
</li>
</ul>
</div>
<div class="card-body">
<div
class="card-body overflow-auto"
style="height: calc(100vh - 105px)"
>
<!-- SETTINGS Nav Tab panes -->
<!-- Station tab contents-->
@ -180,15 +169,6 @@ import settings_exp from "./settings_exp.vue";
<settings_rigcontrol />
</div>
<div
class="tab-pane"
id="hamlib"
role="tabpanel"
aria-labelledby="hamlib-tab"
tabindex="0"
>
<settings_hamlib />
</div>
<div
class="tab-pane"
id="modem"

View file

@ -26,24 +26,6 @@ import { settingsStore as settings } from "../store/settingsStore.js";
</div>
</label>
</div>
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50">Database maintenance</label>
<label class="input-group-text w-50">
<button
class="btn btn-outline-secondary btn-sm w-50"
id="btnCleanDB"
type="button"
disabled
>
Clean</button
>&nbsp;
<div
class="spinner-border text-warning invisible"
role="status"
id="divCleanDBSpinner"
></div>
</label>
</div>
<div class="center">
<div class="badge text-bg-danger">
<i class="bi bi-shield-exclamation"></i> These options may not work and

View file

@ -13,44 +13,6 @@ function saveSettings() {
}
</script>
<template>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-50">GUI theme</span>
<select
class="form-select form-select-sm w-50"
id="theme_selector"
@change="saveSettings"
v-model="settings.local.theme"
>
<option value="default_light">Default (light)</option>
<option value="default_dark">Default (dark)</option>
<option value="default_auto">Default (auto)</option>
<option value="cerulean">Cerulean</option>
<option value="cosmo">Cosmo</option>
<option value="cyborg">Cyborg</option>
<option value="darkly">Darkly</option>
<option value="flatly">Flatly</option>
<option value="journal">Journal</option>
<option value="litera">Litera</option>
<option value="lumen">Lumen</option>
<option value="lux">Lux</option>
<option value="materia">Materia</option>
<option value="minty">Minty</option>
<option value="morph">Morhp</option>
<option value="pulse">Pulse</option>
<option value="quartz">Quartz</option>
<option value="sandstone">Sandstone</option>
<option value="simplex">Simplex</option>
<option value="sketchy">Sketchy</option>
<option value="slate">Slate</option>
<option value="solar">Solar</option>
<option value="spacelab">Spacelab</option>
<option value="superhero">Superhero</option>
<option value="united">United</option>
<option value="vapor">Vapor</option>
<option value="yeti">Yeti</option>
<option value="zephyr">Zephyr</option>
</select>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-50">Waterfall theme</span>
<select
@ -68,34 +30,6 @@ function saveSettings() {
<option value="6">Binary</option>
</select>
</div>
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50">Enable fancy GUI</label>
<label class="input-group-text w-50">
<div class="form-check form-switch form-check-inline">
<input
class="form-check-input"
type="checkbox"
id="GraphicsSwitch"
@change="saveSettings"
v-model="settings.local.high_graphics"
/>
<label class="form-check-label" for="GraphicsSwitch"
>Higher CPU Usage</label
>
</div>
</label>
</div>
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50" for="inputGroupFile02"
>Received files folder</label
>
<input
type="text"
class="form-control w-50"
id="received_files_folder"
disabled
/>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-50">Update channel</span>
<select

View file

@ -42,268 +42,288 @@ import { serialDeviceOptions } from "../js/deviceFormHelper";
v-model.number="settings.remote.RADIO.model_id"
>
<option selected value="-- ignore --">-- ignore --</option>
<option value="2028">Kenwood TS480</option>
<option value="1">Hamlib Dummy</option>
<option value="2">Hamlib NET rigctl</option>
<option value="4">FLRig FLRig</option>
<option value="5">TRXManager TRXManager 5.7.630+</option>
<option value="6">Hamlib Dummy No VFO</option>
<option value="1001">Yaesu FT-847</option>
<option value="1003">Yaesu FT-1000D</option>
<option value="1004">Yaesu MARK-V FT-1000MP</option>
<option value="1005">Yaesu FT-747GX</option>
<option value="1006">Yaesu FT-757GX</option>
<option value="1007">Yaesu FT-757GXII</option>
<option value="1009">Yaesu FT-767GX</option>
<option value="1010">Yaesu FT-736R</option>
<option value="1011">Yaesu FT-840</option>
<option value="1013">Yaesu FT-900</option>
<option value="1014">Yaesu FT-920</option>
<option value="1015">Yaesu FT-890</option>
<option value="1016">Yaesu FT-990</option>
<option value="1017">Yaesu FRG-100</option>
<option value="1018">Yaesu FRG-9600</option>
<option value="1019">Yaesu FRG-8800</option>
<option value="1020">Yaesu FT-817</option>
<option value="1021">Yaesu FT-100</option>
<option value="1022">Yaesu FT-857</option>
<option value="1023">Yaesu FT-897</option>
<option value="1024">Yaesu FT-1000MP</option>
<option value="1025">Yaesu MARK-V Field FT-1000MP</option>
<option value="1026">Yaesu VR-5000</option>
<option value="1027">Yaesu FT-450</option>
<option value="1028">Yaesu FT-950</option>
<option value="1029">Yaesu FT-2000</option>
<option value="1030">Yaesu FTDX-9000</option>
<option value="1031">Yaesu FT-980</option>
<option value="1032">Yaesu FTDX-5000</option>
<option value="1033">Vertex Standard VX-1700</option>
<option value="1034">Yaesu FTDX-1200</option>
<option value="1035">Yaesu FT-991</option>
<option value="1036">Yaesu FT-891</option>
<option value="1037">Yaesu FTDX-3000</option>
<option value="1038">Yaesu FT-847UNI</option>
<option value="1039">Yaesu FT-600</option>
<option value="1040">Yaesu FTDX-101D</option>
<option value="1041">Yaesu FT-818</option>
<option value="1042">Yaesu FTDX-10</option>
<option value="1043">Yaesu FT-897D</option>
<option value="1044">Yaesu FTDX-101MP</option>
<option value="2001">Kenwood TS-50S</option>
<option value="2002">Kenwood TS-440S</option>
<option value="2003">Kenwood TS-450S</option>
<option value="2004">Kenwood TS-570D</option>
<option value="2005">Kenwood TS-690S</option>
<option value="2006">Kenwood TS-711</option>
<option value="2007">Kenwood TS-790</option>
<option value="2008">Kenwood TS-811</option>
<option value="2009">Kenwood TS-850</option>
<option value="2010">Kenwood TS-870S</option>
<option value="2011">Kenwood TS-940S</option>
<option value="2012">Kenwood TS-950S</option>
<option value="2013">Kenwood TS-950SDX</option>
<option value="2014">Kenwood TS-2000</option>
<option value="2015">Kenwood R-5000</option>
<option value="2016">Kenwood TS-570S</option>
<option value="2017">Kenwood TH-D7A</option>
<option value="2019">Kenwood TH-F6A</option>
<option value="2020">Kenwood TH-F7E</option>
<option value="2021">Elecraft K2</option>
<option value="2022">Kenwood TS-930</option>
<option value="2023">Kenwood TH-G71</option>
<option value="2024">Kenwood TS-680S</option>
<option value="2025">Kenwood TS-140S</option>
<option value="2026">Kenwood TM-D700</option>
<option value="2027">Kenwood TM-V7</option>
<option value="2028">Kenwood TS-480</option>
<option value="2029">Elecraft K3</option>
<option value="2030">Kenwood TRC-80</option>
<option value="2031">Kenwood TS-590S</option>
<option value="2032">SigFox Transfox</option>
<option value="2033">Kenwood TH-D72A</option>
<option value="2034">Kenwood TM-D710(G)</option>
<option value="2036">FlexRadio 6xxx</option>
<option value="2037">Kenwood TS-590SG</option>
<option value="2038">Elecraft XG3</option>
<option value="2039">Kenwood TS-990s</option>
<option value="2040">OpenHPSDR PiHPSDR</option>
<option value="2041">Kenwood TS-890S</option>
<option value="2042">Kenwood TH-D74</option>
<option value="2043">Elecraft K3S</option>
<option value="2044">Elecraft KX2</option>
<option value="2045">Elecraft KX3</option>
<option value="2046">Hilberling PT-8000A</option>
<option value="2047">Elecraft K4</option>
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis</option>
<option value="2049">Malachite DSP</option>
<option value="3002">Icom IC-1275</option>
<option value="3003">Icom IC-271</option>
<option value="3004">Icom IC-275</option>
<option value="3006">Icom IC-471</option>
<option value="3007">Icom IC-475</option>
<option value="3009">Icom IC-706</option>
<option value="3010">Icom IC-706MkII</option>
<option value="3011">Icom IC-706MkIIG</option>
<option value="3012">Icom IC-707</option>
<option value="3013">Icom IC-718</option>
<option value="3014">Icom IC-725</option>
<option value="3015">Icom IC-726</option>
<option value="3016">Icom IC-728</option>
<option value="3017">Icom IC-729</option>
<option value="3019">Icom IC-735</option>
<option value="3020">Icom IC-736</option>
<option value="3021">Icom IC-737</option>
<option value="3022">Icom IC-738</option>
<option value="3023">Icom IC-746</option>
<option value="3024">Icom IC-751</option>
<option value="3026">Icom IC-756</option>
<option value="3027">Icom IC-756PRO</option>
<option value="3028">Icom IC-761</option>
<option value="3029">Icom IC-765</option>
<option value="3030">Icom IC-775</option>
<option value="3031">Icom IC-781</option>
<option value="3032">Icom IC-820H</option>
<option value="3034">Icom IC-821H</option>
<option value="3035">Icom IC-970</option>
<option value="3036">Icom IC-R10</option>
<option value="3037">Icom IC-R71</option>
<option value="3038">Icom IC-R72</option>
<option value="3039">Icom IC-R75</option>
<option value="3040">Icom IC-R7000</option>
<option value="3041">Icom IC-R7100</option>
<option value="3042">Icom ICR-8500</option>
<option value="3043">Icom IC-R9000</option>
<option value="3044">Icom IC-910</option>
<option value="3045">Icom IC-78</option>
<option value="3046">Icom IC-746PRO</option>
<option value="3047">Icom IC-756PROII</option>
<option value="3051">Ten-Tec Omni VI Plus</option>
<option value="3052">Optoelectronics OptoScan535</option>
<option value="3053">Optoelectronics OptoScan456</option>
<option value="3054">Icom IC ID-1</option>
<option value="3055">Icom IC-703</option>
<option value="3056">Icom IC-7800</option>
<option value="3057">Icom IC-756PROIII</option>
<option value="3058">Icom IC-R20</option>
<option value="3060">Icom IC-7000</option>
<option value="3061">Icom IC-7200</option>
<option value="3062">Icom IC-7700</option>
<option value="3063">Icom IC-7600</option>
<option value="3064">Ten-Tec Delta II</option>
<option value="3065">Icom IC-92D</option>
<option value="3066">Icom IC-R9500</option>
<option value="3067">Icom IC-7410</option>
<option value="3068">Icom IC-9100</option>
<option value="3069">Icom IC-RX7</option>
<option value="3070">Icom IC-7100</option>
<option value="3071">Icom ID-5100</option>
<option value="3072">Icom IC-2730</option>
<option value="3073">Icom IC-7300</option>
<option value="3074">Microtelecom Perseus</option>
<option value="3075">Icom IC-785x</option>
<option value="3076">Xeigu X108G</option>
<option value="3077">Icom IC-R6</option>
<option value="3078">Icom IC-7610</option>
<option value="3079">Icom IC-R8600</option>
<option value="3080">Icom IC-R30</option>
<option value="3081">Icom IC-9700</option>
<option value="3082">Icom ID-4100</option>
<option value="3083">Icom ID-31</option>
<option value="3084">Icom ID-51</option>
<option value="3085">Icom IC-705</option>
<option value="4001">Icom IC-PCR1000</option>
<option value="4002">Icom IC-PCR100</option>
<option value="4003">Icom IC-PCR1500</option>
<option value="4004">Icom IC-PCR2500</option>
<option value="5001">AOR AR8200</option>
<option value="5002">AOR AR8000</option>
<option value="5003">AOR AR7030</option>
<option value="5004">AOR AR5000</option>
<option value="5005">AOR AR3030</option>
<option value="5006">AOR AR3000A</option>
<option value="5008">AOR AR2700</option>
<option value="5013">AOR AR8600</option>
<option value="5014">AOR AR5000A</option>
<option value="5015">AOR AR7030 Plus</option>
<option value="5016">AOR SR2200</option>
<option value="6005">JRC NRD-525</option>
<option value="6006">JRC NRD-535D</option>
<option value="6007">JRC NRD-545 DSP</option>
<option value="8001">Uniden BC780xlt</option>
<option value="8002">Uniden BC245xlt</option>
<option value="8003">Uniden BC895xlt</option>
<option value="8004">Radio Shack PRO-2052</option>
<option value="8006">Uniden BC250D</option>
<option value="8010">Uniden BCD-396T</option>
<option value="8011">Uniden BCD-996T</option>
<option value="8012">Uniden BC898T</option>
<option value="9002">Drake R-8A</option>
<option value="9003">Drake R-8B</option>
<option value="10004">Lowe HF-235</option>
<option value="11003">Racal RA6790/GM</option>
<option value="11005">Racal RA3702</option>
<option value="12004">Watkins-Johnson WJ-8888</option>
<option value="14002">Skanti TRP8000</option>
<option value="14004">Skanti TRP 8255 S R</option>
<option value="15001">Winradio WR-1000</option>
<option value="15002">Winradio WR-1500</option>
<option value="15003">Winradio WR-1550</option>
<option value="15004">Winradio WR-3100</option>
<option value="15005">Winradio WR-3150</option>
<option value="15006">Winradio WR-3500</option>
<option value="15007">Winradio WR-3700</option>
<option value="15009">Winradio WR-G313</option>
<option value="16001">Ten-Tec TT-550</option>
<option value="16002">Ten-Tec TT-538 Jupiter</option>
<option value="16003">Ten-Tec RX-320</option>
<option value="16004">Ten-Tec RX-340</option>
<option value="16005">Ten-Tec RX-350</option>
<option value="16007">Ten-Tec TT-516 Argonaut V</option>
<option value="16008">Ten-Tec TT-565 Orion</option>
<option value="16009">Ten-Tec TT-585 Paragon</option>
<option value="16011">Ten-Tec TT-588 Omni VII</option>
<option value="16012">Ten-Tec RX-331</option>
<option value="16013">Ten-Tec TT-599 Eagle</option>
<option value="17001">Alinco DX-77</option>
<option value="17002">Alinco DX-SR8</option>
<option value="18001">Kachina 505DSP</option>
<option value="22001">TAPR DSP-10</option>
<option value="23001">Flex-radio SDR-1000</option>
<option value="23003">DTTS Microwave Society DttSP IPC</option>
<option value="23004">DTTS Microwave Society DttSP UDP</option>
<option value="24001">RFT EKD-500</option>
<option value="25001">Elektor Elektor 3/04</option>
<option value="25002">SAT-Schneider DRT1</option>
<option value="25003">Coding Technologies Digital World Traveller</option>
<option value="25006">AmQRP DDS-60</option>
<option value="25007">Elektor Elektor SDR-USB</option>
<option value="25008">mRS miniVNA</option>
<option value="25009">SoftRock Si570 AVR-USB</option>
<option value="25011">KTH-SDR kit Si570 PIC-USB</option>
<option value="25012">FiFi FiFi-SDR</option>
<option value="25013">AMSAT-UK FUNcube Dongle</option>
<option value="25014">N2ADR HiQSDR</option>
<option value="25015">Funkamateur FA-SDR</option>
<option value="25016">AE9RB Si570 Peaberry V1</option>
<option value="25017">AE9RB Si570 Peaberry V2</option>
<option value="25018">AMSAT-UK FUNcube Dongle Pro+</option>
<option value="25019">HobbyPCB RS-HFIQ</option>
<option value="26001">Video4Linux SW/FM radio</option>
<option value="26002">Video4Linux2 SW/FM radio</option>
<option value="27001">Rohde&Schwarz ESMC</option>
<option value="27002">Rohde&Schwarz EB200</option>
<option value="27003">Rohde&Schwarz XK2100</option>
<option value="28001">Philips/Simoco PRM8060</option>
<option value="29001">ADAT www.adat.ch ADT-200A</option>
<option value="30001">Icom IC-M700PRO</option>
<option value="30002">Icom IC-M802</option>
<option value="30003">Icom IC-M710</option>
<option value="30004">Icom IC-M803</option>
<option value="31001">Dorji DRA818V</option>
<option value="31002">Dorji DRA818U</option>
<option value="32001">Barrett 2050</option>
<option value="32002">Barrett 950</option>
<option value="33001">ELAD FDM-DUO</option>
<option value="29001">ADAT www.adat.ch (29001)</option>
<option value="25016">AE9RB Si570 (25016)</option>
<option value="25017">AE9RB Si570 (25017)</option>
<option value="17001">Alinco DX-77 (17001)</option>
<option value="17002">Alinco DX-SR8 (17002)</option>
<option value="25006">AmQRP DDS-60 (25006)</option>
<option value="25013">AMSAT-UK FUNcube (25013)</option>
<option value="25018">AMSAT-UK FUNcube (25018)</option>
<option value="5008">AOR AR2700 (5008)</option>
<option value="5006">AOR AR3000A (5006)</option>
<option value="5005">AOR AR3030 (5005)</option>
<option value="5004">AOR AR5000 (5004)</option>
<option value="5014">AOR AR5000A (5014)</option>
<option value="5003">AOR AR7030 (5003)</option>
<option value="5015">AOR AR7030 (5015)</option>
<option value="5002">AOR AR8000 (5002)</option>
<option value="5001">AOR AR8200 (5001)</option>
<option value="5013">AOR AR8600 (5013)</option>
<option value="5016">AOR SR2200 (5016)</option>
<option value="32001">Barrett 2050 (32001)</option>
<option value="32003">Barrett 4050 (32003)</option>
<option value="32002">Barrett 950 (32002)</option>
<option value="34001">CODAN Envoy (34001)</option>
<option value="34002">CODAN NGT (34002)</option>
<option value="25003">Coding Technologies (25003)</option>
<option value="31002">Dorji DRA818U (31002)</option>
<option value="31001">Dorji DRA818V (31001)</option>
<option value="9002">Drake R-8A (9002)</option>
<option value="9003">Drake R-8B (9003)</option>
<option value="23003">DTTS Microwave (23003)</option>
<option value="23004">DTTS Microwave (23004)</option>
<option value="33001">ELAD FDM-DUO (33001)</option>
<option value="2021">Elecraft K2 (2021)</option>
<option value="2029">Elecraft K3 (2029)</option>
<option value="2043">Elecraft K3S (2043)</option>
<option value="2047">Elecraft K4 (2047)</option>
<option value="2044">Elecraft KX2 (2044)</option>
<option value="2045">Elecraft KX3 (2045)</option>
<option value="2038">Elecraft XG3 (2038)</option>
<option value="25001">Elektor Elektor (25001)</option>
<option value="25007">Elektor Elektor (25007)</option>
<option value="25012">FiFi FiFi-SDR (25012)</option>
<option value="2036">FlexRadio 6xxx (2036)</option>
<option value="23001">Flex-radio SDR-1000 (23001)</option>
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis (2048)</option>
<option value="25015">Funkamateur FA-SDR (25015)</option>
<option value="35001">GOMSPACE GS100 (35001)</option>
<option value="2046">Hilberling PT-8000A (2046)</option>
<option value="25019">HobbyPCB RS-HFIQ (25019)</option>
<option value="3054">Icom IC (3054)</option>
<option value="3002">Icom IC-1275 (3002)</option>
<option value="3003">Icom IC-271 (3003)</option>
<option value="3072">Icom IC-2730 (3072)</option>
<option value="3004">Icom IC-275 (3004)</option>
<option value="3005">Icom IC-375 (3005)</option>
<option value="3006">Icom IC-471 (3006)</option>
<option value="3007">Icom IC-475 (3007)</option>
<option value="3008">Icom IC-575 (3008)</option>
<option value="3060">Icom IC-7000 (3060)</option>
<option value="3055">Icom IC-703 (3055)</option>
<option value="3085">Icom IC-705 (3085)</option>
<option value="3009">Icom IC-706 (3009)</option>
<option value="3010">Icom IC-706MkII (3010)</option>
<option value="3011">Icom IC-706MkIIG (3011)</option>
<option value="3012">Icom IC-707 (3012)</option>
<option value="3070">Icom IC-7100 (3070)</option>
<option value="3013">Icom IC-718 (3013)</option>
<option value="3061">Icom IC-7200 (3061)</option>
<option value="3014">Icom IC-725 (3014)</option>
<option value="3015">Icom IC-726 (3015)</option>
<option value="3016">Icom IC-728 (3016)</option>
<option value="3017">Icom IC-729 (3017)</option>
<option value="3073">Icom IC-7300 (3073)</option>
<option value="3019">Icom IC-735 (3019)</option>
<option value="3020">Icom IC-736 (3020)</option>
<option value="3021">Icom IC-737 (3021)</option>
<option value="3022">Icom IC-738 (3022)</option>
<option value="3067">Icom IC-7410 (3067)</option>
<option value="3023">Icom IC-746 (3023)</option>
<option value="3046">Icom IC-746PRO (3046)</option>
<option value="3024">Icom IC-751 (3024)</option>
<option value="3026">Icom IC-756 (3026)</option>
<option value="3027">Icom IC-756PRO (3027)</option>
<option value="3047">Icom IC-756PROII (3047)</option>
<option value="3057">Icom IC-756PROIII (3057)</option>
<option value="3063">Icom IC-7600 (3063)</option>
<option value="3028">Icom IC-761 (3028)</option>
<option value="3078">Icom IC-7610 (3078)</option>
<option value="3029">Icom IC-765 (3029)</option>
<option value="3062">Icom IC-7700 (3062)</option>
<option value="3030">Icom IC-775 (3030)</option>
<option value="3045">Icom IC-78 (3045)</option>
<option value="3056">Icom IC-7800 (3056)</option>
<option value="3031">Icom IC-781 (3031)</option>
<option value="3075">Icom IC-7850/7851 (3075)</option>
<option value="3032">Icom IC-820H (3032)</option>
<option value="3034">Icom IC-821H (3034)</option>
<option value="3044">Icom IC-910 (3044)</option>
<option value="3068">Icom IC-9100 (3068)</option>
<option value="3065">Icom IC-92D (3065)</option>
<option value="3035">Icom IC-970 (3035)</option>
<option value="3081">Icom IC-9700 (3081)</option>
<option value="3086">Icom IC-F8101 (3086)</option>
<option value="30001">Icom IC-M700PRO (30001)</option>
<option value="30003">Icom IC-M710 (30003)</option>
<option value="30002">Icom IC-M802 (30002)</option>
<option value="30004">Icom IC-M803 (30004)</option>
<option value="4002">Icom IC-PCR100 (4002)</option>
<option value="4001">Icom IC-PCR1000 (4001)</option>
<option value="4003">Icom IC-PCR1500 (4003)</option>
<option value="4004">Icom IC-PCR2500 (4004)</option>
<option value="3036">Icom IC-R10 (3036)</option>
<option value="3058">Icom IC-R20 (3058)</option>
<option value="3080">Icom IC-R30 (3080)</option>
<option value="3077">Icom IC-R6 (3077)</option>
<option value="3040">Icom IC-R7000 (3040)</option>
<option value="3037">Icom IC-R71 (3037)</option>
<option value="3041">Icom IC-R7100 (3041)</option>
<option value="3038">Icom IC-R72 (3038)</option>
<option value="3039">Icom IC-R75 (3039)</option>
<option value="3042">Icom ICR-8500 (3042)</option>
<option value="3079">Icom IC-R8600 (3079)</option>
<option value="3043">Icom IC-R9000 (3043)</option>
<option value="3066">Icom IC-R9500 (3066)</option>
<option value="3069">Icom IC-RX7 (3069)</option>
<option value="3083">Icom ID-31 (3083)</option>
<option value="3082">Icom ID-4100 (3082)</option>
<option value="3084">Icom ID-51 (3084)</option>
<option value="3071">Icom ID-5100 (3071)</option>
<option value="6001">JRC JST-145 (6001)</option>
<option value="6002">JRC JST-245 (6002)</option>
<option value="6005">JRC NRD-525 (6005)</option>
<option value="6006">JRC NRD-535D (6006)</option>
<option value="6007">JRC NRD-545 (6007)</option>
<option value="18001">Kachina 505DSP (18001)</option>
<option value="2015">Kenwood R-5000 (2015)</option>
<option value="2033">Kenwood TH-D72A (2033)</option>
<option value="2042">Kenwood TH-D74 (2042)</option>
<option value="2017">Kenwood TH-D7A (2017)</option>
<option value="2019">Kenwood TH-F6A (2019)</option>
<option value="2020">Kenwood TH-F7E (2020)</option>
<option value="2023">Kenwood TH-G71 (2023)</option>
<option value="2026">Kenwood TM-D700 (2026)</option>
<option value="2034">Kenwood TM-D710(G) (2034)</option>
<option value="2027">Kenwood TM-V7 (2027)</option>
<option value="2035">Kenwood TM-V71(A) (2035)</option>
<option value="2030">Kenwood TRC-80 (2030)</option>
<option value="2025">Kenwood TS-140S (2025)</option>
<option value="2014">Kenwood TS-2000 (2014)</option>
<option value="2002">Kenwood TS-440S (2002)</option>
<option value="2003">Kenwood TS-450S (2003)</option>
<option value="2028">Kenwood TS-480 (2028)</option>
<option value="2001">Kenwood TS-50S (2001)</option>
<option value="2004">Kenwood TS-570D (2004)</option>
<option value="2016">Kenwood TS-570S (2016)</option>
<option value="2031">Kenwood TS-590S (2031)</option>
<option value="2037">Kenwood TS-590SG (2037)</option>
<option value="2024">Kenwood TS-680S (2024)</option>
<option value="2005">Kenwood TS-690S (2005)</option>
<option value="2006">Kenwood TS-711 (2006)</option>
<option value="2007">Kenwood TS-790 (2007)</option>
<option value="2008">Kenwood TS-811 (2008)</option>
<option value="2009">Kenwood TS-850 (2009)</option>
<option value="2010">Kenwood TS-870S (2010)</option>
<option value="2041">Kenwood TS-890S (2041)</option>
<option value="2022">Kenwood TS-930 (2022)</option>
<option value="2011">Kenwood TS-940S (2011)</option>
<option value="2012">Kenwood TS-950S (2012)</option>
<option value="2013">Kenwood TS-950SDX (2013)</option>
<option value="2039">Kenwood TS-990S (2039)</option>
<option value="25011">KTH-SDR kit (25011)</option>
<option value="2050">Lab599 TX-500 (2050)</option>
<option value="10004">Lowe HF-235 (10004)</option>
<option value="1045">M0NKA mcHF (1045)</option>
<option value="2049">Malachite DSP (2049)</option>
<option value="3074">Microtelecom Perseus (3074)</option>
<option value="25008">mRS miniVNA (25008)</option>
<option value="25014">N2ADR HiQSDR (25014)</option>
<option value="2040">OpenHPSDR PiHPSDR (2040)</option>
<option value="3053">Optoelectronics OptoScan456 (3053)</option>
<option value="3052">Optoelectronics OptoScan535 (3052)</option>
<option value="28001">Philips/Simoco PRM8060 (28001)</option>
<option value="11005">Racal RA3702 (11005)</option>
<option value="11003">Racal RA6790/GM (11003)</option>
<option value="8004">Radio Shack (8004)</option>
<option value="24001">RFT EKD-500 (24001)</option>
<option value="27002">Rohde&Schwarz EB200 (27002)</option>
<option value="27004">Rohde&Schwarz EK895/6 (27004)</option>
<option value="27001">Rohde&Schwarz ESMC (27001)</option>
<option value="27003">Rohde&Schwarz XK2100 (27003)</option>
<option value="25002">SAT-Schneider DRT1 (25002)</option>
<option value="2051">SDRPlay SDRUno (2051)</option>
<option value="2032">SigFox Transfox (2032)</option>
<option value="14004">Skanti TRP (14004)</option>
<option value="14002">Skanti TRP8000 (14002)</option>
<option value="25009">SoftRock Si570 (25009)</option>
<option value="22001">TAPR DSP-10 (22001)</option>
<option value="3064">Ten-Tec Delta (3064)</option>
<option value="3051">Ten-Tec Omni (3051)</option>
<option value="16003">Ten-Tec RX-320 (16003)</option>
<option value="16012">Ten-Tec RX-331 (16012)</option>
<option value="16004">Ten-Tec RX-340 (16004)</option>
<option value="16005">Ten-Tec RX-350 (16005)</option>
<option value="16007">Ten-Tec TT-516 (16007)</option>
<option value="16002">Ten-Tec TT-538 (16002)</option>
<option value="16001">Ten-Tec TT-550 (16001)</option>
<option value="16008">Ten-Tec TT-565 (16008)</option>
<option value="16009">Ten-Tec TT-585 (16009)</option>
<option value="16011">Ten-Tec TT-588 (16011)</option>
<option value="16013">Ten-Tec TT-599 (16013)</option>
<option value="8002">Uniden BC245xlt (8002)</option>
<option value="8006">Uniden BC250D (8006)</option>
<option value="8001">Uniden BC780xlt (8001)</option>
<option value="8003">Uniden BC895xlt (8003)</option>
<option value="8012">Uniden BC898T (8012)</option>
<option value="8010">Uniden BCD-396T (8010)</option>
<option value="8011">Uniden BCD-996T (8011)</option>
<option value="1033">Vertex Standard (1033)</option>
<option value="26001">Video4Linux SW/FM (26001)</option>
<option value="26002">Video4Linux2 SW/FM (26002)</option>
<option value="12004">Watkins-Johnson WJ-8888 (12004)</option>
<option value="15001">Winradio WR-1000 (15001)</option>
<option value="15002">Winradio WR-1500 (15002)</option>
<option value="15003">Winradio WR-1550 (15003)</option>
<option value="15004">Winradio WR-3100 (15004)</option>
<option value="15005">Winradio WR-3150 (15005)</option>
<option value="15006">Winradio WR-3500 (15006)</option>
<option value="15007">Winradio WR-3700 (15007)</option>
<option value="15009">Winradio WR-G313 (15009)</option>
<option value="3088">Xiegu G90 (3088)</option>
<option value="3076">Xiegu X108G (3076)</option>
<option value="3089">Xiegu X5105 (3089)</option>
<option value="3087">Xiegu X6100 (3087)</option>
<option value="1017">Yaesu FRG-100 (1017)</option>
<option value="1019">Yaesu FRG-8800 (1019)</option>
<option value="1018">Yaesu FRG-9600 (1018)</option>
<option value="1021">Yaesu FT-100 (1021)</option>
<option value="1003">Yaesu FT-1000D (1003)</option>
<option value="1024">Yaesu FT-1000MP (1024)</option>
<option value="1029">Yaesu FT-2000 (1029)</option>
<option value="1027">Yaesu FT-450 (1027)</option>
<option value="1046">Yaesu FT-450D (1046)</option>
<option value="1039">Yaesu FT-600 (1039)</option>
<option value="1047">Yaesu FT-650 (1047)</option>
<option value="1049">Yaesu FT-710 (1049)</option>
<option value="1010">Yaesu FT-736R (1010)</option>
<option value="1005">Yaesu FT-747GX (1005)</option>
<option value="1006">Yaesu FT-757GX (1006)</option>
<option value="1007">Yaesu FT-757GXII (1007)</option>
<option value="1009">Yaesu FT-767GX (1009)</option>
<option value="1020">Yaesu FT-817 (1020)</option>
<option value="1041">Yaesu FT-818 (1041)</option>
<option value="1011">Yaesu FT-840 (1011)</option>
<option value="1001">Yaesu FT-847 (1001)</option>
<option value="1038">Yaesu FT-847UNI (1038)</option>
<option value="1022">Yaesu FT-857 (1022)</option>
<option value="1015">Yaesu FT-890 (1015)</option>
<option value="1036">Yaesu FT-891 (1036)</option>
<option value="1023">Yaesu FT-897 (1023)</option>
<option value="1043">Yaesu FT-897D (1043)</option>
<option value="1013">Yaesu FT-900 (1013)</option>
<option value="1014">Yaesu FT-920 (1014)</option>
<option value="1028">Yaesu FT-950 (1028)</option>
<option value="1031">Yaesu FT-980 (1031)</option>
<option value="1016">Yaesu FT-990 (1016)</option>
<option value="1048">Yaesu FT-990 (1048)</option>
<option value="1035">Yaesu FT-991 (1035)</option>
<option value="1042">Yaesu FTDX-10 (1042)</option>
<option value="1040">Yaesu FTDX-101D (1040)</option>
<option value="1044">Yaesu FTDX-101MP (1044)</option>
<option value="1034">Yaesu FTDX-1200 (1034)</option>
<option value="1037">Yaesu FTDX-3000 (1037)</option>
<option value="1032">Yaesu FTDX-5000 (1032)</option>
<option value="1030">Yaesu FTDX-9000 (1030)</option>
<option value="1004">Yaesu MARK-V (1004)</option>
<option value="1025">Yaesu MARK-V (1025)</option>
<option value="1026">Yaesu VR-5000 (1026)</option>
</select>
</div>

View file

@ -94,7 +94,41 @@ import { audioInputOptions, audioOutputOptions } from "../js/deviceFormHelper";
</option>
</select>
</div>
<!-- Audio rx level-->
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-25">RX Audio Level</span>
<span class="input-group-text w-25">{{
settings.remote.AUDIO.rx_audio_level
}}</span>
<span class="input-group-text w-50">
<input
type="range"
class="form-range"
min="-30"
max="20"
step="1"
id="audioLevelRX"
@change="onChange"
v-model.number="settings.remote.AUDIO.rx_audio_level"
/></span>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-25">TX Audio Level</span>
<span class="input-group-text w-25">{{
settings.remote.AUDIO.tx_audio_level
}}</span>
<span class="input-group-text w-50">
<input
type="range"
class="form-range"
min="-30"
max="20"
step="1"
id="audioLevelTX"
@change="onChange"
v-model.number="settings.remote.AUDIO.tx_audio_level"
/></span>
</div>
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50">TX delay in ms</label>
<select

View file

@ -1,5 +1,7 @@
<script setup lang="ts">
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import settings_hamlib from "./settings_hamlib.vue";
import settings_tci from "./settings_tci.vue";
</script>
<template>
@ -23,29 +25,54 @@ import { settingsStore as settings, onChange } from "../store/settingsStore.js";
<hr class="m-2" />
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">TCI IP Address</span>
<input
type="text"
class="form-control"
placeholder="TCI IP"
id="rigcontrol_tci_ip"
aria-label="Device IP"
@change="onChange"
v-model="settings.remote.TCI.tci_ip"
/>
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button
class="nav-link active"
id="nav-home-tab"
data-bs-toggle="tab"
data-bs-target="#nav-hamlib"
type="button"
role="tab"
aria-controls="nav-home"
aria-selected="true"
>
Hamlib
</button>
<button
class="nav-link"
id="nav-profile-tab"
data-bs-toggle="tab"
data-bs-target="#nav-tci"
type="button"
role="tab"
aria-controls="nav-profile"
aria-selected="false"
>
TCI
</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div
class="tab-pane fade show active"
id="nav-hamlib"
role="tabpanel"
aria-labelledby="nav-hamlib-tab"
tabindex="0"
>
<settings_hamlib />
</div>
<div
class="tab-pane fade"
id="nav-tci"
role="tabpanel"
aria-labelledby="nav-tci-tab"
tabindex="0"
>
<settings_tci />
</div>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">TCI port</span>
<input
type="text"
class="form-control"
placeholder="TCI port"
id="rigcontrol_tci_port"
aria-label="Device Port"
@change="onChange"
v-model="settings.remote.TCI.tci_port"
/>
</div>
<hr class="m-2" />
</template>

View file

@ -1,5 +1,20 @@
<script setup lang="ts">
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import {
settingsStore as settings,
onChange,
getRemote,
} from "../store/settingsStore.js";
import {
validateCallsignWithSSID,
validateCallsignWithoutSSID,
} from "../js/freedata";
function validateCall() {
if (validateCallsignWithoutSSID(settings.remote.STATION.mycall))
//Send new callsign to modem if valid
onChange();
//Reload settings from modem as invalid callsign was passed in
else getRemote();
}
</script>
<template>
<!-- station callsign -->
@ -14,8 +29,8 @@ import { settingsStore as settings, onChange } from "../store/settingsStore.js";
id="myCall"
aria-label="Station Callsign"
aria-describedby="basic-addon1"
@change="onChange"
v-model="settings.remote.STATION.mycall"
@change="validateCall"
/>
</div>

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import { serialDeviceOptions } from "../js/deviceFormHelper";
</script>
<template>
<hr class="m-2" />
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">TCI IP Address</span>
<input
type="text"
class="form-control"
placeholder="TCI IP"
id="rigcontrol_tci_ip"
aria-label="Device IP"
@change="onChange"
v-model="settings.remote.TCI.tci_ip"
/>
</div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">TCI port</span>
<input
type="text"
class="form-control"
placeholder="TCI port"
id="rigcontrol_tci_port"
aria-label="Device Port"
@change="onChange"
v-model="settings.remote.TCI.tci_port"
/>
</div>
</template>

View file

@ -117,12 +117,13 @@ export function getModemState() {
return apiGet("/modem/state");
}
export function setModemFrequency(newFrequency) {
console.error("setModemFrequency needs implemented");
export function setRadioParameters(frequency, mode, rf_level) {
return apiPost("/radio", {
radio_frequency: frequency,
radio_mode: mode,
radio_rf_level: rf_level,
});
}
export function setModemRigMode(mode) {
console.error("setModemRigMode needs implemented");
}
export function setModemRigPowerLvl(power) {
console.error("setModemRigPowerLvl needs implemented");
export function getRadioStatus() {
return apiGet("/radio");
}

View file

@ -1,5 +1,3 @@
import { addDataToWaterfall } from "../js/waterfallHandler.js";
import {
newMessageReceived,
newBeaconReceived,
@ -23,8 +21,10 @@ export function connectionFailed(endpoint, event) {
}
export function stateDispatcher(data) {
data = JSON.parse(data);
//console.log(data);
//console.debug(data);
if (data["type"] == "state-change" || data["type"] == "state") {
stateStore.modem_connection = "connected";
stateStore.channel_busy = data["channel_busy"];
stateStore.is_codec2_traffic = data["is_codec2_traffic"];
stateStore.is_modem_running = data["is_modem_running"];
@ -32,6 +32,12 @@ export function stateDispatcher(data) {
stateStore.dbfs_level_percent = Math.round(
Math.pow(10, data["audio_dbfs"] / 20) * 100,
);
stateStore.s_meter_strength_raw = Math.round(data["s_meter_strength"]);
stateStore.s_meter_strength_percent = Math.round(
Math.pow(10, data["s_meter_strength"] / 20) * 100,
);
stateStore.channel_busy_slot = data["channel_busy_slot"];
stateStore.beacon_state = data["is_beacon_running"];
stateStore.radio_status = data["radio_status"];
@ -45,7 +51,7 @@ export function stateDispatcher(data) {
export function eventDispatcher(data) {
data = JSON.parse(data);
//console.info(data);
console.debug(data);
if (data["scatter"] !== undefined) {
stateStore.scatter = JSON.parse(data["scatter"]);
@ -99,22 +105,31 @@ export function eventDispatcher(data) {
case "NEW":
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
displayToast("success", "bi-check-circle", message, 5000);
stateStore.dxcallsign = data["arq-transfer-outbound"].dxcall;
stateStore.arq_transmission_percent = 0;
stateStore.arq_total_bytes = 0;
return;
case "OPEN_SENT":
console.log("state OPEN_SENT needs to be implemented");
console.info("state OPEN_SENT needs to be implemented");
return;
case "INFO_SENT":
console.log("state INFO_SENT needs to be implemented");
console.info("state INFO_SENT needs to be implemented");
return;
case "BURST_SENT":
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Received Bytes: ${data["arq-transfer-outbound"].received_bytes}/${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
displayToast("info", "bi-info-circle", message, 5000);
stateStore.arq_transmission_percent =
(data["arq-transfer-outbound"].received_bytes /
data["arq-transfer-outbound"].total_bytes) *
100;
stateStore.arq_total_bytes =
data["arq-transfer-outbound"].received_bytes;
return;
case "ABORTING":
console.log("state ABORTING needs to be implemented");
console.info("state ABORTING needs to be implemented");
return;
case "ABORTED":
@ -150,21 +165,42 @@ export function eventDispatcher(data) {
case "NEW":
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, State: ${data["arq-transfer-inbound"].state}`;
displayToast("info", "bi-info-circle", message, 5000);
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
stateStore.arq_transmission_percent = 0;
stateStore.arq_total_bytes = 0;
return;
case "OPEN_ACK_SENT":
message = `Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Total Bytes: ${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
displayToast("info", "bi-arrow-left-right", message, 5000);
stateStore.arq_transmission_percent =
(data["arq-transfer-inbound"].received_bytes /
data["arq-transfer-inbound"].total_bytes) *
100;
stateStore.arq_total_bytes =
data["arq-transfer-inbound"].received_bytes;
return;
case "INFO_ACK_SENT":
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
displayToast("info", "bi-info-circle", message, 5000);
stateStore.arq_transmission_percent =
(data["arq-transfer-inbound"].received_bytes /
data["arq-transfer-inbound"].total_bytes) *
100;
stateStore.arq_total_bytes =
data["arq-transfer-inbound"].received_bytes;
return;
case "BURST_REPLY_SENT":
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
displayToast("info", "bi-info-circle", message, 5000);
stateStore.arq_transmission_percent =
(data["arq-transfer-inbound"].received_bytes /
data["arq-transfer-inbound"].total_bytes) *
100;
stateStore.arq_total_bytes =
data["arq-transfer-inbound"].received_bytes;
return;
case "ENDED":
@ -175,10 +211,16 @@ export function eventDispatcher(data) {
data["arq-transfer-inbound"].data,
data["arq-transfer-inbound"],
);
stateStore.arq_transmission_percent =
(data["arq-transfer-inbound"].received_bytes /
data["arq-transfer-inbound"].total_bytes) *
100;
stateStore.arq_total_bytes =
data["arq-transfer-inbound"].received_bytes;
return;
case "ABORTED":
console.log("state ABORTED needs to be implemented");
console.info("state ABORTED needs to be implemented");
return;
case "FAILED":

View file

@ -61,7 +61,7 @@ export function sortByPropertyDesc(property) {
* @returns true or false if callsign appears to be valid with an SSID
*/
export function validateCallsignWithSSID(callsign: string) {
var patt = new RegExp("^[A-Z,a-z]+[0-9][A-Z,a-z]*-(1[0-5]|[0-9])$");
var patt = new RegExp("^[A-Z]+[0-9][A-Z]*-(1[0-5]|[0-9])$");
if (
callsign === undefined ||
@ -82,7 +82,7 @@ export function validateCallsignWithSSID(callsign: string) {
* @returns true or false if callsign appears to be valid without an SSID
*/
export function validateCallsignWithoutSSID(callsign: string) {
var patt = new RegExp("^[A-Za-z]+[0-9][A-Za-z]+$");
var patt = new RegExp("^[A-Z]+[0-9][A-Z]+$");
if (
callsign === undefined ||

View file

@ -42,9 +42,9 @@ export const useChatStore = defineStore("chatStore", () => {
});
var inputText = ref("");
var inputFile = ref();
var inputFileName = ref();
var inputFileType = ref();
var inputFileSize = ref();
var inputFileName = ref("-");
var inputFileType = ref("-");
var inputFileSize = ref("-");
var callsign_list = ref();
var sorted_chat_list = ref();

View file

@ -17,11 +17,10 @@ nconf.defaults({
port: "5000",
spectrum: "waterfall",
wf_theme: 2,
theme: "default_light",
high_graphics: true,
update_channel: "alpha",
enable_sys_notification: false,
grid_layout: "[]",
grid_preset: "[]",
},
});
@ -33,11 +32,10 @@ export const settingsStore = reactive({
port: "5000",
spectrum: "waterfall",
wf_theme: 2,
theme: "default_light",
high_graphics: true,
update_channel: "alpha",
enable_sys_notification: false,
grid_layout: "[]",
grid_preset: "[]",
},
remote: {
AUDIO: {

View file

@ -69,9 +69,6 @@ export const useStateStore = defineStore("stateStore", () => {
modem_connection.value = state;
if (modem_connection.value == "open") {
//Set tuning for fancy graphics mode (high/low CPU)
//set_CPU_mode();
//GUI will auto connect to TNC if already running, if that is the case increment start count if 0
if (modemStartCount.value == 0) modemStartCount.value++;
}

View file

@ -15,6 +15,14 @@ export default defineConfig(({ command }) => {
const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
return {
optimizeDeps: {
esbuildOptions: {
target: "esnext",
},
},
build: {
target: "esnext",
},
plugins: [
vue(),
electron([

View file

@ -4,6 +4,7 @@ import data_frame_factory
import structlog
from event_manager import EventManager
from modem_frametypes import FRAME_TYPE
import time
class ARQSession():
@ -44,6 +45,9 @@ class ARQSession():
self.event_frame_received = threading.Event()
self.id = None
self.session_started = time.time()
self.session_ended = 0
self.session_max_age = 500
def log(self, message, isWarning = False):
msg = f"[{type(self).__name__}][id={self.id}][state={self.state}]: {message}"
@ -89,3 +93,29 @@ class ARQSession():
self.log(f"Ignoring unknow transition from state {self.state.name} with frame {frame['frame_type']}")
def is_session_outdated(self):
session_alivetime = time.time() - self.session_max_age
if self.session_ended < session_alivetime and self.state.name in ['FAILED', 'ENDED', 'ABORTED']:
return True
return False
def calculate_session_duration(self):
return self.session_ended - self.session_started
def calculate_session_statistics(self):
duration = self.calculate_session_duration()
total_bytes = self.total_length
# self.total_length
duration_in_minutes = duration / 60 # Convert duration from seconds to minutes
# Calculate bytes per minute
if duration_in_minutes > 0:
bytes_per_minute = int(total_bytes / duration_in_minutes)
else:
bytes_per_minute = 0
return {
'total_bytes': total_bytes,
'duration': duration,
'bytes_per_minute': bytes_per_minute
}

View file

@ -4,7 +4,7 @@ import helpers
from modem_frametypes import FRAME_TYPE
from codec2 import FREEDV_MODE
from enum import Enum
import time
class IRS_State(Enum):
NEW = 0
OPEN_ACK_SENT = 1
@ -66,6 +66,7 @@ class ARQSessionIRS(arq_session.ARQSession):
self.version = 1
self.state = IRS_State.NEW
self.state_enum = IRS_State # needed for access State enum from outside
self.total_length = 0
self.total_crc = ''
@ -93,12 +94,13 @@ class ARQSessionIRS(arq_session.ARQSession):
self.log(f"Waiting {timeout} seconds...")
if not self.event_frame_received.wait(timeout):
self.log("Timeout waiting for ISS. Session failed.")
self.session_ended = time.time()
self.set_state(IRS_State.FAILED)
self.event_manager.send_arq_session_finished(False, self.id, self.dxcall, self.total_length, False, self.state.name)
self.event_manager.send_arq_session_finished(False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
def launch_transmit_and_wait(self, frame, timeout, mode):
thread_wait = threading.Thread(target = self.transmit_and_wait,
args = [frame, timeout, mode])
args = [frame, timeout, mode], daemon=True)
thread_wait.start()
def send_open_ack(self, open_frame):
@ -185,9 +187,10 @@ class ARQSessionIRS(arq_session.ARQSession):
flag_checksum=True)
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
self.log("ACK sent")
self.session_ended = time.time()
self.set_state(IRS_State.ENDED)
self.event_manager.send_arq_session_finished(
False, self.id, self.dxcall, self.total_length, True, self.state.name, data=self.received_data)
False, self.id, self.dxcall, True, self.state.name, data=self.received_data, statistics=self.calculate_session_statistics())
else:
@ -200,9 +203,10 @@ class ARQSessionIRS(arq_session.ARQSession):
flag_checksum=False)
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
self.log("CRC fail at the end of transmission!")
self.session_ended = time.time()
self.set_state(IRS_State.FAILED)
self.event_manager.send_arq_session_finished(
False, self.id, self.dxcall, self.total_length, False, self.state.name)
False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
def calibrate_speed_settings(self):
@ -226,4 +230,4 @@ class ARQSessionIRS(arq_session.ARQSession):
self.launch_transmit_and_wait(stop_ack, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(IRS_State.ABORTED)
self.event_manager.send_arq_session_finished(
False, self.id, self.dxcall, self.total_length, False, self.state.name)
False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())

View file

@ -7,6 +7,7 @@ from modem_frametypes import FRAME_TYPE
import arq_session
import helpers
from enum import Enum
import time
class ISS_State(Enum):
NEW = 0
@ -56,11 +57,13 @@ class ARQSessionISS(arq_session.ARQSession):
super().__init__(config, modem, dxcall)
self.state_manager = state_manager
self.data = data
self.total_length = len(data)
self.data_crc = ''
self.confirmed_bytes = 0
self.state = ISS_State.NEW
self.state_enum = ISS_State # needed for access State enum from outside
self.id = self.generate_id()
self.frame_factory = data_frame_factory.DataFrameFactory(self.config)
@ -92,12 +95,12 @@ class ARQSessionISS(arq_session.ARQSession):
self.transmission_failed()
def launch_twr(self, frame_or_burst, timeout, retries, mode):
twr = threading.Thread(target = self.transmit_wait_and_retry, args=[frame_or_burst, timeout, retries, mode])
twr = threading.Thread(target = self.transmit_wait_and_retry, args=[frame_or_burst, timeout, retries, mode], daemon=True)
twr.start()
def start(self):
self.event_manager.send_arq_session_new(
True, self.id, self.dxcall, len(self.data), self.state.name)
True, self.id, self.dxcall, self.total_length, self.state.name)
session_open_frame = self.frame_factory.build_arq_session_open(self.dxcall, self.id)
self.launch_twr(session_open_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(ISS_State.OPEN_SENT)
@ -114,7 +117,7 @@ class ARQSessionISS(arq_session.ARQSession):
self.transmission_aborted(irs_frame)
return
info_frame = self.frame_factory.build_arq_session_info(self.id, len(self.data),
info_frame = self.frame_factory.build_arq_session_info(self.id, self.total_length,
helpers.get_crc_32(self.data),
self.snr[0])
@ -127,9 +130,9 @@ class ARQSessionISS(arq_session.ARQSession):
if 'offset' in irs_frame:
self.confirmed_bytes = irs_frame['offset']
self.log(f"IRS confirmed {self.confirmed_bytes}/{len(self.data)} bytes")
self.log(f"IRS confirmed {self.confirmed_bytes}/{self.total_length} bytes")
self.event_manager.send_arq_session_progress(
True, self.id, self.dxcall, self.confirmed_bytes, len(self.data), self.state.name)
True, self.id, self.dxcall, self.confirmed_bytes, self.total_length, self.state.name)
# check if we received an abort flag
if irs_frame["flag"]["ABORT"]:
@ -137,7 +140,7 @@ class ARQSessionISS(arq_session.ARQSession):
return
if irs_frame["flag"]["FINAL"]:
if self.confirmed_bytes == len(self.data) and irs_frame["flag"]["CHECKSUM"]:
if self.confirmed_bytes == self.total_length and irs_frame["flag"]["CHECKSUM"]:
self.transmission_ended(irs_frame)
return
else:
@ -158,15 +161,18 @@ class ARQSessionISS(arq_session.ARQSession):
def transmission_ended(self, irs_frame):
# final function for sucessfully ended transmissions
self.session_ended = time.time()
self.set_state(ISS_State.ENDED)
self.log(f"All data transfered! flag_final={irs_frame['flag']['FINAL']}, flag_checksum={irs_frame['flag']['CHECKSUM']}")
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall, len(self.data),True, self.state.name)
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,True, self.state.name, statistics=self.calculate_session_statistics())
self.state_manager.remove_arq_iss_session(self.id)
def transmission_failed(self, irs_frame=None):
# final function for failed transmissions
self.session_ended = time.time()
self.set_state(ISS_State.FAILED)
self.log(f"Transmission failed!")
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall, len(self.data),False, self.state.name)
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,False, self.state.name, statistics=self.calculate_session_statistics())
def abort_transmission(self, irs_frame=None):
# function for starting the abort sequence
@ -174,7 +180,7 @@ class ARQSessionISS(arq_session.ARQSession):
self.set_state(ISS_State.ABORTING)
self.event_manager.send_arq_session_finished(
True, self.id, self.dxcall, len(self.data), False, self.state.name)
True, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
# break actual retries
self.event_frame_received.set()
@ -188,9 +194,12 @@ class ARQSessionISS(arq_session.ARQSession):
def transmission_aborted(self, irs_frame):
self.log("session aborted")
self.session_ended = time.time()
self.set_state(ISS_State.ABORTED)
# break actual retries
self.event_frame_received.set()
self.event_manager.send_arq_session_finished(
True, self.id, self.dxcall, len(self.data), False, self.state.name)
True, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
self.state_manager.remove_arq_iss_session(self.id)

View file

@ -1,47 +1,41 @@
import threading
import data_frame_factory
import command_beacon
import sched
import time
import threading
class Beacon:
BEACON_LOOP_INTERVAL = 1
def __init__(self, config, states, event_manager, logger, modem):
self.modem_config = config
self.config = config
self.states = states
self.event_manager = event_manager
self.log = logger
self.modem = modem
self.loop_running = True
self.paused = False
self.thread = None
self.scheduler = sched.scheduler(time.time, time.sleep)
self.beacon_interval = self.config['MODEM']['beacon_interval']
self.beacon_enabled = False
self.event = threading.Event()
self.frame_factory = data_frame_factory.DataFrameFactory(config)
def start(self):
beacon_thread = threading.Thread(target=self.run_beacon, name="beacon", daemon=True)
beacon_thread.start()
self.beacon_enabled = True
self.schedule_beacon()
def stop(self):
self.loop_running = False
self.beacon_enabled = False
def schedule_beacon(self):
if self.beacon_enabled:
self.scheduler.enter(self.beacon_interval, 1, self.run_beacon)
threading.Thread(target=self.scheduler.run, daemon=True).start()
def run_beacon(self):
if self.beacon_enabled:
# Your beacon logic here
cmd = command_beacon.BeaconCommand(self.config, self.states, self.event_manager)
cmd.run(self.event_manager, self.modem)
self.schedule_beacon() # Reschedule the next beacon
def refresh(self):
self.event.set()
self.event.clear()
def run_beacon(self) -> None:
while self.loop_running:
while (self.states.is_beacon_running and
not self.paused and
True):
#not self.states.channel_busy):
cmd = command_beacon.BeaconCommand(self.modem_config, self.states, self.event_manager)
cmd.run(self.event_manager, self.modem)
self.event.wait(self.modem_config['MODEM']['beacon_interval'])
self.event.wait(self.BEACON_LOOP_INTERVAL)
# Interrupt and reschedule the beacon
self.scheduler = sched.scheduler(time.time, time.sleep)
self.schedule_beacon()

View file

@ -101,13 +101,14 @@ for file in files:
#log.info("[C2 ] Libcodec2 loaded", path=file)
break
except OSError as err:
log.info("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err)
pass
#log.info("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err)
# Quit module if codec2 cant be loaded
if api is None or "api" not in locals():
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
sys.exit(1)
log.info("[C2 ] Libcodec2 loaded...")
# ctypes function init
# api.freedv_set_tuning_range.restype = ctypes.c_int

View file

@ -7,4 +7,4 @@ class TestCommand(TxCommand):
return self.frame_factory.build_test()
def get_tx_mode(self):
return codec2.FREEDV_MODE.datac13.value
return codec2.FREEDV_MODE.datac3

View file

@ -138,9 +138,9 @@ class Demodulator():
def sd_input_audio_callback(self, indata: np.ndarray, frames: int, time, status) -> None:
audio_48k = np.frombuffer(indata, dtype=np.int16)
audio_8k = self.resampler.resample48_to_8(audio_48k)
audio.calculate_fft(audio_8k, self.fft_queue, self.states)
audio_8k_level_adjusted = audio.set_audio_volume(audio_8k, self.rx_audio_level)
audio.calculate_fft(audio_8k_level_adjusted, self.fft_queue, self.states)
length_audio_8k_level_adjusted = len(audio_8k_level_adjusted)
# Avoid buffer overflow by filling only if buffer for

View file

@ -56,7 +56,9 @@ class EventManager:
}
self.broadcast(event)
def send_arq_session_finished(self, outbound: bool, session_id, dxcall, total_bytes, success: bool, state: bool, data=False):
def send_arq_session_finished(self, outbound: bool, session_id, dxcall, success: bool, state: bool, data=False, statistics=None):
if statistics is None:
statistics = {}
if data:
data = base64.b64encode(data).decode("UTF-8")
direction = 'outbound' if outbound else 'inbound'
@ -65,7 +67,7 @@ class EventManager:
f"arq-transfer-{direction}": {
'session_id': session_id,
'dxcall': dxcall,
'total_bytes': total_bytes,
'statistics': statistics,
'success': bool(success),
'state': state,
'data': data

View file

@ -11,6 +11,8 @@ import requests
import threading
import ujson as json
import structlog
import sched
import time
log = structlog.get_logger("explorer")
@ -21,18 +23,16 @@ class explorer():
self.states = states
self.explorer_url = "https://api.freedata.app/explorer.php"
self.publish_interval = 120
self.enable_explorer = config["STATION"]["enable_explorer"]
if self.enable_explorer:
self.interval_thread = threading.Thread(target=self.interval, name="interval", daemon=True)
self.interval_thread.start()
self.scheduler = sched.scheduler(time.time, time.sleep)
self.schedule_thread = threading.Thread(target=self.run_scheduler)
self.schedule_thread.start()
def interval(self):
# Wait for just a little bit incase modem is contionously restarting due to a bug or user configuration issue
threading.Event().wait(30)
while True:
self.push()
threading.Event().wait(self.publish_interval)
def run_scheduler(self):
# Schedule the first execution of push
self.scheduler.enter(self.publish_interval, 1, self.push)
# Run the scheduler in a loop
self.scheduler.run()
def push(self):
@ -43,7 +43,7 @@ class explorer():
version = str(self.app.MODEM_VERSION)
bandwidth = str(self.config['MODEM']['enable_low_bandwidth_mode'])
beacon = str(self.states.is_beacon_running)
strength = str(self.states.radio_strength)
strength = str(self.states.s_meter_strength)
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
@ -67,8 +67,14 @@ class explorer():
station_data = json.dumps(station_data)
try:
response = requests.post(self.explorer_url, json=station_data, headers=headers)
# print(response.status_code)
# print(response.content)
except Exception as e:
log.warning("[EXPLORER] connection lost")
# Reschedule the push method
self.scheduler.enter(self.publish_interval, 1, self.push)
def shutdown(self):
# If there are other cleanup tasks, include them here
if self.schedule_thread:
self.schedule_thread.join()

View file

@ -97,41 +97,3 @@ class DISPATCHER():
if data[:1] == FR_TYPE.ARQ_SESSION_OPEN:
return data[13:14]
return None

View file

@ -28,6 +28,42 @@ class FrameHandler():
'bytes_per_frame': 0
}
def is_frame_for_me(self):
call_with_ssid = self.config['STATION']['mycall'] + "-" + str(self.config['STATION']['myssid'])
ft = self.details['frame']['frame_type']
print(self.details)
# Check for callsign checksum
if ft in ['ARQ_SESSION_OPEN', 'ARQ_SESSION_OPEN_ACK', 'PING', 'PING_ACK']:
valid, mycallsign = helpers.check_callsign(
call_with_ssid,
self.details["frame"]["destination_crc"],
self.config['STATION']['ssid_list'])
# Check for session id on IRS side
elif ft in ['ARQ_SESSION_INFO', 'ARQ_BURST_FRAME', 'ARQ_STOP']:
session_id = self.details['frame']['session_id']
if session_id in self.states.arq_irs_sessions:
valid = True
# Check for session id on ISS side
elif ft in ['ARQ_SESSION_INFO_ACK', 'ARQ_BURST_ACK', 'ARQ_STOP_ACK']:
session_id = self.details['frame']['session_id']
if session_id in self.states.arq_iss_sessions:
valid = True
else:
valid = False
if not valid:
self.logger.info(f"[Frame handler] {ft} received but not for us.")
return valid
def should_respond(self):
return self.is_frame_for_me()
def add_to_activity_list(self):

View file

@ -9,6 +9,10 @@ from arq_session_iss import ARQSessionISS
class ARQFrameHandler(frame_handler.FrameHandler):
def follow_protocol(self):
if not self.should_respond():
return
frame = self.details['frame']
session_id = frame['session_id']
snr = self.details["snr"]
@ -22,6 +26,9 @@ class ARQFrameHandler(frame_handler.FrameHandler):
# Normal case when receiving a SESSION_OPEN for the first time
else:
if self.states.check_if_running_arq_session():
self.logger.warning("DISCARDING SESSION OPEN because of ongoing ARQ session ", frame=frame)
return
session = ARQSessionIRS(self.config,
self.modem,
frame['origin'],

View file

@ -4,21 +4,20 @@ import data_frame_factory
class PingFrameHandler(frame_handler.FrameHandler):
def is_frame_for_me(self):
call_with_ssid = self.config['STATION']['mycall'] + "-" + str(self.config['STATION']['myssid'])
valid, mycallsign = helpers.check_callsign(
call_with_ssid,
self.details["frame"]["destination_crc"],
self.config['STATION']['ssid_list'])
#def is_frame_for_me(self):
# call_with_ssid = self.config['STATION']['mycall'] + "-" + str(self.config['STATION']['myssid'])
# valid, mycallsign = helpers.check_callsign(
# call_with_ssid,
# self.details["frame"]["destination_crc"],
# self.config['STATION']['ssid_list'])
if not valid:
ft = self.details['frame']['frame_type']
self.logger.info(f"[Modem] {ft} received but not for us.")
return valid
# if not valid:
# ft = self.details['frame']['frame_type']
# self.logger.info(f"[Modem] {ft} received but not for us.")
# return valid
def should_respond(self):
return self.is_frame_for_me()
#def should_respond(self):
# return self.is_frame_for_me()
def follow_protocol(self):

View file

@ -93,15 +93,9 @@ def get_crc_24(data: str) -> bytes:
"""
if not isinstance(data, (bytes)) or isinstance(data, (bytearray)):
data = bytes(data,'utf-8')
crc_algorithm = crcengine.create(
0x864CFB,
24,
0xB704CE,
ref_in=False,
ref_out=False,
xor_out=0,
name="crc-24-openpgp",
)
params = crcengine.CrcParams(0x864cfb, 24, 0xb704ce, reflect_in=False, reflect_out=False, xor_out=0)
crc_algorithm = crcengine.create(params=params)
return crc_algorithm(data).to_bytes(3,byteorder="big")
@ -302,25 +296,28 @@ def check_callsign(callsign: str, crc_to_check: bytes, ssid_list):
[True, Callsign + SSID]
False
"""
print(callsign)
if not isinstance(callsign, (bytes)):
callsign = bytes(callsign,'utf-8')
log.debug("[HLP] check_callsign: Checking:", callsign=callsign)
try:
# We want the callsign without SSID
callsign = callsign.split(b"-")[0]
splitted_callsign = callsign.split(b"-")
callsign = splitted_callsign[0]
ssid = splitted_callsign[1].decode()
except IndexError:
# This is expected when `callsign` doesn't have a dash.
pass
ssid = 0
except Exception as err:
log.debug("[HLP] check_callsign: Error converting to bytes:", e=err)
# ensure, we are always have the own ssid in ssid_list even if it is empty
if ssid not in ssid_list:
ssid_list.append(str(ssid))
for ssid in ssid_list:
call_with_ssid = callsign + b'-' + (str(ssid)).encode('utf-8')
#call_with_ssid.extend("-".encode("utf-8"))
#call_with_ssid.extend(str(ssid).encode("utf-8"))
callsign_crc = get_crc_24(call_with_ssid)
callsign_crc = callsign_crc.hex()
@ -328,6 +325,7 @@ def check_callsign(callsign: str, crc_to_check: bytes, ssid_list):
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid, checksum=crc_to_check)
return [True, call_with_ssid.decode()]
log.debug("[HLP] check_callsign: Checking:", callsign=callsign, crc_to_check=crc_to_check, own_crc=callsign_crc)
return [False, b'']

View file

@ -31,11 +31,12 @@ class RF:
log = structlog.get_logger("RF")
def __init__(self, config, event_manager, fft_queue, service_queue, states) -> None:
def __init__(self, config, event_manager, fft_queue, service_queue, states, radio_manager) -> None:
self.config = config
self.service_queue = service_queue
self.states = states
self.event_manager = event_manager
self.radio = radio_manager
self.sampler_avg = 0
self.buffer_avg = 0
@ -108,7 +109,6 @@ class RF:
# Initialize codec2, rig control, and data threads
self.init_codec2()
self.init_rig_control()
self.init_data_threads()
return True
@ -423,73 +423,3 @@ class RF:
else:
sd.play(audio_48k, blocksize=4096, blocking=True)
return
def init_rig_control(self):
# Check how we want to control the radio
if self.radiocontrol == "rigctld":
import rigctld as rig
elif self.radiocontrol == "tci":
self.radio = self.tci_module
else:
import rigdummy as rig
if not self.radiocontrol in ["tci"]:
self.radio = rig.radio()
self.radio.open_rig(
rigctld_ip=self.rigctld_ip,
rigctld_port=self.rigctld_port,
)
hamlib_thread = threading.Thread(
target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True
)
hamlib_thread.start()
hamlib_set_thread = threading.Thread(
target=self.set_rig_data, name="HAMLIB_SET_THREAD", daemon=True
)
hamlib_set_thread.start()
def set_rig_data(self) -> None:
"""
Set rigctld parameters like frequency, mode
THis needs to be processed in a queue
"""
while True:
cmd = RIGCTLD_COMMAND_QUEUE.get()
if cmd[0] == "set_frequency":
# [1] = Frequency
self.radio.set_frequency(cmd[1])
if cmd[0] == "set_mode":
# [1] = Mode
self.radio.set_mode(cmd[1])
def update_rig_data(self) -> None:
"""
Request information about the current state of the radio via hamlib
"""
while True:
try:
# this looks weird, but is necessary for avoiding rigctld packet colission sock
#threading.Event().wait(0.1)
self.states.set("radio_status", self.radio.get_status())
#threading.Event().wait(0.25)
self.states.set("radio_frequency", self.radio.get_frequency())
threading.Event().wait(0.1)
self.states.set("radio_mode", self.radio.get_mode())
threading.Event().wait(0.1)
self.states.set("radio_bandwidth", self.radio.get_bandwidth())
threading.Event().wait(0.1)
if self.states.isTransmitting():
self.radio_alc = self.radio.get_alc()
threading.Event().wait(0.1)
self.states.set("radio_rf_power", self.radio.get_level())
threading.Event().wait(0.1)
self.states.set("radio_strength", self.radio.get_strength())
except Exception as e:
self.log.warning(
"[MDM] error getting radio data",
e=e,
)
threading.Event().wait(1)

59
modem/radio_manager.py Normal file
View file

@ -0,0 +1,59 @@
import rigctld
import tci
import rigdummy
import time
import threading
class RadioManager:
def __init__(self, config, state_manager, event_manager):
self.config = config
self.state_manager = state_manager
self.event_manager = event_manager
self.radiocontrol = config['RADIO']['control']
self.rigctld_ip = config['RIGCTLD']['ip']
self.rigctld_port = config['RIGCTLD']['port']
self.refresh_rate = 1
self.stop_event = threading.Event()
self.update_thread = threading.Thread(target=self.update_parameters, daemon=True)
self._init_rig_control()
def _init_rig_control(self):
# Check how we want to control the radio
if self.radiocontrol == "rigctld":
self.radio = rigctld.radio(self.state_manager, hostname=self.rigctld_ip,port=self.rigctld_port)
elif self.radiocontrol == "tci":
raise NotImplementedError
# self.radio = self.tci_module
else:
self.radio = rigdummy.radio()
self.update_thread.start()
def set_ptt(self, state):
self.radio.set_ptt(state)
def set_frequency(self, frequency):
self.radio.set_frequency(frequency)
def set_mode(self, mode):
self.radio.set_mode(mode)
def set_rf_level(self, level):
self.radio.set_rf_level(level)
def update_parameters(self):
while not self.stop_event.is_set():
parameters = self.radio.get_parameters()
self.state_manager.set("radio_frequency", parameters['frequency'])
self.state_manager.set("radio_mode", parameters['mode'])
self.state_manager.set("radio_bandwidth", parameters['bandwidth'])
self.state_manager.set("radio_rf_level", parameters['rf'])
if self.state_manager.isTransmitting():
self.radio_alc = parameters['alc']
self.state_manager.set("s_meter_strength", parameters['strength'])
time.sleep(self.refresh_rate)
def stop(self):
self.radio.disconnect()
self.stop_event.set()

View file

@ -1,12 +1,6 @@
#!/usr/bin/env python3
# class taken from darksidelemm
# rigctl - https://github.com/darksidelemm/rotctld-web-gui/blob/master/rotatorgui.py#L35
#
# modified and adjusted to FreeDATA needs by DJ2LS
import contextlib
import socket
import structlog
import time
import threading
class radio:
@ -14,332 +8,198 @@ class radio:
log = structlog.get_logger("radio (rigctld)")
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
"""Open a connection to rigctld, and test it for validity"""
self.ptt_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ptt_connected = False
self.data_connected = False
def __init__(self, states, hostname="localhost", port=4532, timeout=5):
self.hostname = hostname
self.port = port
self.connection_attempts = 5
self.timeout = timeout
self.states = states
# class wide variable for some parameters
self.bandwidth = ''
self.frequency = ''
self.mode = ''
self.alc = ''
self.strength = ''
self.rf = ''
self.connection = None
self.connected = False
self.await_response = threading.Event()
self.await_response.set()
def open_rig(
self,
rigctld_ip,
rigctld_port
):
"""
self.parameters = {
'frequency': '---',
'mode': '---',
'alc': '---',
'strength': '---',
'bandwidth': '---',
'rf': '---',
'ptt': False # Initial PTT state is set to False
}
Args:
rigctld_ip:
rigctld_port:
# connect to radio
self.connect()
Returns:
def connect(self):
try:
self.connection = socket.create_connection((self.hostname, self.port), timeout=self.timeout)
self.connected = True
self.states.set("radio_status", True)
self.log.info(f"[RIGCTLD] Connected to rigctld at {self.hostname}:{self.port}")
except Exception as err:
self.log.warning(f"[RIGCTLD] Failed to connect to rigctld: {err}")
self.connected = False
self.states.set("radio_status", False)
"""
self.hostname = rigctld_ip
self.port = int(rigctld_port)
def disconnect(self):
self.connected = False
self.connection.close()
del self.connection
self.connection = None
self.states.set("radio_status", False)
self.parameters = {
'frequency': '---',
'mode': '---',
'alc': '---',
'strength': '---',
'bandwidth': '---',
'rf': '---',
'ptt': False # Initial PTT state is set to False
}
# _ptt_connect = self.ptt_connect()
# _data_connect = self.data_connect()
ptt_thread = threading.Thread(target=self.ptt_connect, args=[], daemon=True)
ptt_thread.start()
data_thread = threading.Thread(target=self.data_connect, args=[], daemon=True)
data_thread.start()
# wait some time
threading.Event().wait(0.5)
if self.ptt_connected and self.data_connected:
self.log.debug("Rigctl DATA/PTT initialized")
return True
self.log.error(
"[RIGCTLD] Can't connect!", ip=self.hostname, port=self.port
)
return False
def ptt_connect(self):
"""Connect to rigctld instance"""
while True:
if not self.ptt_connected:
try:
self.ptt_connection = socket.create_connection((self.hostname, self.port))
self.ptt_connected = True
self.log.info(
"[RIGCTLD] Connected PTT instance to rigctld!", ip=self.hostname, port=self.port
)
except Exception as err:
# ConnectionRefusedError: [Errno 111] Connection refused
self.close_rig()
self.log.warning(
"[RIGCTLD] PTT Reconnect...",
ip=self.hostname,
port=self.port,
e=err,
)
threading.Event().wait(0.5)
def data_connect(self):
"""Connect to rigctld instance"""
while True:
if not self.data_connected:
try:
self.data_connection = socket.create_connection((self.hostname, self.port))
self.data_connected = True
self.log.info(
"[RIGCTLD] Connected DATA instance to rigctld!", ip=self.hostname, port=self.port
)
except Exception as err:
# ConnectionRefusedError: [Errno 111] Connection refused
self.close_rig()
self.log.warning(
"[RIGCTLD] DATA Reconnect...",
ip=self.hostname,
port=self.port,
e=err,
)
threading.Event().wait(0.5)
def close_rig(self):
""" """
self.ptt_sock.close()
self.data_sock.close()
self.ptt_connected = False
self.data_connected = False
def send_ptt_command(self, command, expect_answer) -> bytes:
"""Send a command to the connected rotctld instance,
and return the return value.
Args:
command:
"""
if self.ptt_connected:
try:
self.ptt_connection.sendall(command + b"\n")
except Exception:
self.log.warning(
"[RIGCTLD] Command not executed!",
command=command,
ip=self.hostname,
port=self.port,
)
self.ptt_connected = False
return b""
def send_data_command(self, command, expect_answer) -> bytes:
"""Send a command to the connected rotctld instance,
and return the return value.
Args:
command:
"""
if self.data_connected:
self.data_connection.setblocking(False)
#Allow a little more time for a response from rigctld before generating a timeout, seems to have no ill effects on a well behaving setup and fixes Issue #373
self.data_connection.settimeout(0.30)
try:
self.data_connection.sendall(command + b"\n")
except Exception:
self.log.warning(
"[RIGCTLD] Command not executed!",
command=command,
ip=self.hostname,
port=self.port,
)
self.data_connected = False
def send_command(self, command) -> str:
if self.connected:
# wait if we have another command awaiting its response...
self.await_response.wait()
try:
# recv seems to be blocking so in case of ptt we don't need the response
# maybe this speeds things up and avoids blocking states
recv = True
data = b''
self.await_response = threading.Event()
self.connection.sendall(command.encode('utf-8') + b"\n")
response = self.connection.recv(1024)
self.await_response.set()
return response.decode('utf-8').strip()
except Exception as err:
self.log.warning(f"[RIGCTLD] Error sending command [{command}] to rigctld: {err}")
self.connected = False
while recv:
try:
data = self.data_connection.recv(4800)
except socket.timeout:
recv = False
return data
# return self.data_connection.recv(64) if expect_answer else True
except Exception:
self.log.warning(
"[RIGCTLD] No command response!",
command=command,
ip=self.hostname,
port=self.port,
)
self.data_connected = False
return b""
def get_status(self):
""" """
return True if self.data_connected and self.ptt_connected else False
def get_level(self):
try:
data = self.send_data_command(b"l RF", True)
data = data.split(b"\n")
rf = data[0].decode("utf-8")
if 'RPRT' not in rf:
try:
self.rf = str(rf)
except ValueError:
self.rf = str(rf)
return self.rf
except Exception:
return self.rf
def get_strength(self):
try:
data = self.send_data_command(b"l STRENGTH", True)
data = data.split(b"\n")
strength = data[0].decode("utf-8")
if 'RPRT' not in strength:
try:
self.strength = str(strength)
except ValueError:
self.strength = str(strength)
return self.strength
except Exception:
return self.strength
def get_alc(self):
try:
data = self.send_data_command(b"l ALC", True)
data = data.split(b"\n")
alc = data[0].decode("utf-8")
if 'RPRT' not in alc:
try:
alc = float(alc)
except ValueError:
self.alc = 0.0
return self.alc
except Exception:
return self.alc
def get_mode(self):
""" """
try:
data = self.send_data_command(b"m", True)
data = data.split(b"\n")
data = data[0].decode("utf-8")
if 'RPRT' not in data:
try:
data = int(data)
except ValueError:
self.mode = str(data)
return self.mode
except Exception:
return self.mode
def get_bandwidth(self):
""" """
try:
data = self.send_data_command(b"m", True)
data = data.split(b"\n")
data = data[1].decode("utf-8")
if 'RPRT' not in data and data not in ['']:
with contextlib.suppress(ValueError):
self.bandwidth = int(data)
return self.bandwidth
except Exception:
return self.bandwidth
def get_frequency(self):
""" """
try:
data = self.send_data_command(b"f", True)
data = data.decode("utf-8")
if 'RPRT' not in data and data not in [0, '0', '']:
with contextlib.suppress(ValueError):
data = int(data)
# make sure we have a frequency and not bandwidth
if data >= 10000:
self.frequency = data
return self.frequency
except Exception:
return self.frequency
def get_ptt(self):
""" """
try:
return self.send_data_command(b"t", True)
except Exception:
return False
return ""
def set_ptt(self, state):
"""
"""Set the PTT (Push-to-Talk) state.
Args:
state:
state (bool): True to enable PTT, False to disable.
Returns:
bool: True if the PTT state was set successfully, False otherwise.
"""
try:
if state:
self.send_ptt_command(b"T 1", False)
else:
self.send_ptt_command(b"T 0", False)
return state
except Exception:
return False
def set_frequency(self, frequency):
"""
Args:
frequency:
Returns:
"""
try:
command = bytes(f"F {frequency}", "utf-8")
self.send_data_command(command, False)
except Exception:
return False
if self.connected:
try:
if state:
self.send_command('T 1') # Enable PTT
else:
self.send_command('T 0') # Disable PTT
self.parameters['ptt'] = state # Update PTT state in parameters
return True
except Exception as err:
self.log.warning(f"[RIGCTLD] Error setting PTT state: {err}")
self.connected = False
return False
def set_mode(self, mode):
"""
"""Set the mode.
Args:
mode:
mode (str): The mode to set.
Returns:
bool: True if the mode was set successfully, False otherwise.
"""
try:
command = bytes(f"M {mode} {self.bandwidth}", "utf-8")
self.send_data_command(command, False)
except Exception:
return False
if self.connected:
try:
command = f"M {mode} 0"
self.send_command(command)
self.parameters['mode'] = mode
return True
except Exception as err:
self.log.warning(f"[RIGCTLD] Error setting mode: {err}")
self.connected = False
return False
def set_frequency(self, frequency):
"""Set the frequency.
Args:
frequency (str): The frequency to set.
Returns:
bool: True if the frequency was set successfully, False otherwise.
"""
if self.connected:
try:
command = f"F {frequency}"
self.send_command(command)
self.parameters['frequency'] = frequency
return True
except Exception as err:
self.log.warning(f"[RIGCTLD] Error setting frequency: {err}")
self.connected = False
return False
def set_bandwidth(self, bandwidth):
"""Set the bandwidth.
Args:
bandwidth (str): The bandwidth to set.
Returns:
bool: True if the bandwidth was set successfully, False otherwise.
"""
if self.connected:
try:
command = f"M {self.parameters['mode']} {bandwidth}"
self.send_command(command)
self.parameters['bandwidth'] = bandwidth
return True
except Exception as err:
self.log.warning(f"[RIGCTLD] Error setting bandwidth: {err}")
self.connected = False
return False
def set_rf_level(self, rf):
"""Set the RF.
Args:
rf (str): The RF to set.
Returns:
bool: True if the RF was set successfully, False otherwise.
"""
if self.connected:
try:
command = f"L RFPOWER {rf/100}" #RF RFPOWER --> RFPOWER == IC705
self.send_command(command)
self.parameters['rf'] = rf
return True
except Exception as err:
self.log.warning(f"[RIGCTLD] Error setting RF: {err}")
self.connected = False
return False
def get_parameters(self):
if not self.connected:
self.connect()
if self.connected:
self.parameters['frequency'] = self.send_command('f')
response = self.send_command(
'm').strip() # Get the mode/bandwidth response and remove leading/trailing spaces
try:
mode, bandwidth = response.split('\n', 1) # Split the response into mode and bandwidth
except ValueError:
print(response)
mode = 'err'
bandwidth = 'err'
self.parameters['mode'] = mode
self.parameters['bandwidth'] = bandwidth
self.parameters['alc'] = self.send_command('l ALC')
self.parameters['strength'] = self.send_command('l STRENGTH')
self.parameters['rf'] = self.send_command('l RFPOWER') # RF, RFPOWER
"""Return the latest fetched parameters."""
return self.parameters

View file

@ -1,13 +1,30 @@
hamlib_version = 0
class radio:
""" """
def __init__(self):
pass
self.parameters = {
'frequency': '---',
'mode': '---',
'alc': '---',
'strength': '---',
'bandwidth': '---',
'rf': '---',
'ptt': False # Initial PTT state is set to False
}
def open_rig(self, **kwargs):
def connect(self, **kwargs):
"""
Args:
**kwargs:
Returns:
"""
return True
def disconnect(self, **kwargs):
"""
Args:
@ -98,3 +115,7 @@ class radio:
def close_rig(self):
""" """
return
def get_parameters(self):
return self.parameters

View file

@ -11,7 +11,6 @@ import state_manager
import ujson as json
import websocket_manager as wsm
import api_validations as validations
import command_ping
import command_cq
import command_ping
import command_feq
@ -23,10 +22,7 @@ app = Flask(__name__)
CORS(app)
CORS(app, resources={r"/*": {"origins": "*"}})
sock = Sock(app)
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
# define global MODEM_VERSION
app.MODEM_VERSION = "0.12.0-alpha"
MODEM_VERSION = "0.12.0-alpha"
# set config file to use
def set_config():
@ -41,25 +37,10 @@ def set_config():
print(f"Config file '{config_file}' not found. Exiting.")
exit(1)
app.config_manager = CONFIG(config_file)
return config_file
set_config()
# start modem
app.state_queue = queue.Queue() # queue which holds latest states
app.modem_events = queue.Queue() # queue which holds latest events
app.modem_fft = queue.Queue() # queue which holds latest fft data
app.modem_service = queue.Queue() # start / stop modem service
app.event_manager = event_manager.EventManager([app.modem_events]) # TODO remove the app.modem_event custom queue
# init state manager
app.state_manager = state_manager.StateManager(app.state_queue)
# start service manager
app.service_manager = service_manager.SM(app)
# start modem service
app.modem_service.put("start")
# returns a standard API response
def api_response(data, status = 200):
@ -95,7 +76,7 @@ def index():
return api_response({'name': 'FreeDATA API',
'description': '',
'api_version': 1,
'modem_version': app.MODEM_VERSION,
'modem_version': MODEM_VERSION,
'license': 'GPL3.0',
'documentation': 'https://wiki.freedata.app',
})
@ -221,6 +202,8 @@ def post_modem_send_raw():
return api_response({"info": "endpoint for SENDING RAW DATA via POST"})
if not app.state_manager.is_modem_running:
api_abort('Modem not running', 503)
if app.state_manager.check_if_running_arq_session():
api_abort('Modem busy', 503)
if enqueue_tx_command(command_arq_raw.ARQRawCommand, request.json):
return api_response(request.json)
else:
@ -240,15 +223,21 @@ def post_modem_send_raw_stop():
return api_response(request.json)
@app.route('/radio', methods=['GET', 'POST'])
def get_post_radio():
if request.method in ['POST']:
app.radio_manager.set_frequency(request.json['radio_frequency'])
app.radio_manager.set_mode(request.json['radio_mode'])
app.radio_manager.set_rf_level(int(request.json['radio_rf_level']))
return api_response(request.json)
elif request.method == 'GET':
return api_response(app.state_manager.get_radio_status())
# @app.route('/modem/arq_connect', methods=['POST'])
# @app.route('/modem/arq_disconnect', methods=['POST'])
# @app.route('/modem/send_raw', methods=['POST'])
# @app.route('/modem/stop_transmission', methods=['POST'])
# @app.route('/modem/listen', methods=['POST']) # not needed if we are restarting modem on changing settings
# @app.route('/modem/record_audio', methods=['POST'])
# @app.route('/modem/responde_to_call', methods=['POST']) # not needed if we are restarting modem on changing settings
# @app.route('/modem/responde_to_cq', methods=['POST']) # not needed if we are restarting modem on changing settings
# @app.route('/modem/audio_levels', methods=['POST']) # tx and rx # not needed if we are restarting modem on changing settings
# @app.route('/modem/mesh_ping', methods=['POST'])
# @app.route('/mesh/routing_table', methods=['GET'])
@ -272,4 +261,28 @@ def sock_fft(sock):
def sock_states(sock):
wsm.handle_connection(sock, wsm.states_client_list, app.state_queue)
wsm.startThreads(app)
if __name__ == "__main__":
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
# define global MODEM_VERSION
app.MODEM_VERSION = MODEM_VERSION
config_file = set_config()
app.config_manager = CONFIG(config_file)
# start modem
app.state_queue = queue.Queue() # queue which holds latest states
app.modem_events = queue.Queue() # queue which holds latest events
app.modem_fft = queue.Queue() # queue which holds latest fft data
app.modem_service = queue.Queue() # start / stop modem service
app.event_manager = event_manager.EventManager([app.modem_events]) # TODO remove the app.modem_event custom queue
# init state manager
app.state_manager = state_manager.StateManager(app.state_queue)
# start service manager
app.service_manager = service_manager.SM(app)
# start modem service
app.modem_service.put("start")
wsm.startThreads(app)
app.run()

View file

@ -6,19 +6,21 @@ import audio
import ujson as json
import explorer
import beacon
import radio_manager
class SM:
def __init__(self, app):
self.log = structlog.get_logger("service")
self.app = app
self.modem = False
self.beacon = False
self.app = app
self.explorer = False
self.app.radio_manager = False
self.config = self.app.config_manager.read()
self.modem_fft = app.modem_fft
self.modem_service = app.modem_service
self.states = app.state_manager
self.state_manager = app.state_manager
self.event_manager = app.event_manager
@ -27,27 +29,37 @@ class SM:
)
runner_thread.start()
self.start_explorer_publishing()
def runner(self):
while True:
cmd = self.modem_service.get()
if cmd in ['start'] and not self.modem:
self.log.info("------------------ FreeDATA ------------------")
self.log.info("------------------ MODEM ------------------")
self.config = self.app.config_manager.read()
self.start_radio_manager()
self.start_modem()
self.start_explorer_publishing()
elif cmd in ['stop'] and self.modem:
self.stop_modem()
self.stop_explorer_publishing()
self.stop_radio_manager()
# we need to wait a bit for avoiding a portaudio crash
threading.Event().wait(0.5)
elif cmd in ['restart']:
self.stop_modem()
self.stop_explorer_publishing()
self.stop_radio_manager()
# we need to wait a bit for avoiding a portaudio crash
threading.Event().wait(0.5)
self.config = self.app.config_manager.read()
self.start_radio_manager()
if self.start_modem():
self.event_manager.modem_restarted()
self.start_explorer_publishing()
elif cmd in ['start_beacon']:
self.start_beacon()
@ -57,37 +69,34 @@ class SM:
else:
self.log.warning("[SVC] modem command processing failed", cmd=cmd, state=self.states.is_modem_running)
self.log.warning("[SVC] modem command processing failed", cmd=cmd, state=self.state_manager.is_modem_running)
def start_modem(self):
# read config
self.config = self.app.config_manager.read()
if self.states.is_modem_running:
if self.state_manager.is_modem_running:
self.log.warning("modem already running")
return False
# test audio devices
audio_test = self.test_audio()
if False in audio_test or None in audio_test or self.states.is_modem_running:
if False in audio_test or None in audio_test or self.state_manager.is_modem_running:
self.log.warning("starting modem failed", input_test=audio_test[0], output_test=audio_test[1])
self.states.set("is_modem_running", False)
self.state_manager.set("is_modem_running", False)
self.event_manager.modem_failed()
return False
self.log.info("starting modem....")
self.modem = modem.RF(self.config, self.event_manager, self.modem_fft, self.modem_service, self.states)
self.modem = modem.RF(self.config, self.event_manager, self.modem_fft, self.modem_service, self.state_manager, self.app.radio_manager)
self.frame_dispatcher = frame_dispatcher.DISPATCHER(self.config,
self.event_manager,
self.states,
self.state_manager,
self.modem)
self.frame_dispatcher.start()
self.event_manager.modem_started()
self.states.set("is_modem_running", True)
self.state_manager.set("is_modem_running", True)
self.modem.start_modem()
return True
@ -96,7 +105,7 @@ class SM:
self.log.info("stopping modem....")
del self.modem
self.modem = False
self.states.set("is_modem_running", False)
self.state_manager.set("is_modem_running", False)
self.event_manager.modem_stopped()
def test_audio(self):
@ -111,16 +120,30 @@ class SM:
return [False, False]
def start_beacon(self):
self.beacon = beacon.Beacon(self.config, self.states, self.event_manager, self.log, self.modem)
self.beacon = beacon.Beacon(self.config, self.state_manager, self.event_manager, self.log, self.modem)
self.beacon.start()
def stop_beacon(self):
del self.beacon
self.beacon.stop()
def start_explorer_publishing(self):
try:
# optionally start explorer module
if self.config['STATION']['enable_explorer']:
explorer.explorer(self.app, self.config, self.states)
self.explorer = explorer.explorer(self.app, self.config, self.state_manager)
except Exception as e:
self.log.warning("[EXPLORER] Publishin not started because of error", e=e)
self.log.warning("[EXPLORER] Publishing not started because of error", e=e)
def stop_explorer_publishing(self):
if self.config['STATION']['enable_explorer']:
try:
del self.explorer
except Exception as e:
self.log.info("[EXPLORER] Error while stopping...", e=e)
def start_radio_manager(self):
self.app.radio_manager = radio_manager.RadioManager(self.config, self.state_manager, self.event_manager)
def stop_radio_manager(self):
self.app.radio_manager.stop()
del self.app.radio_manager

View file

@ -21,8 +21,6 @@ class StateManager:
self.is_modem_running = False
self.is_modem_busy = False
self.is_beacon_running = False
self.is_arq_state = False
self.is_arq_session = False
# If true, any wait() call is blocking
self.transmitting_event = threading.Event()
@ -43,8 +41,8 @@ class StateManager:
self.radio_frequency = 0
self.radio_mode = None
self.radio_bandwidth = 0
self.radio_rf_power = 0
self.radio_strength = 0
self.radio_rf_level = 0
self.s_meter_strength = 0
# Set rig control status regardless or rig control method
self.radio_status = False
@ -85,6 +83,7 @@ class StateManager:
"radio_status": self.radio_status,
"radio_frequency": self.radio_frequency,
"radio_mode": self.radio_mode,
"s_meter_strength": self.s_meter_strength,
"channel_busy_slot": self.channel_busy_slot,
"audio_dbfs": self.audio_dbfs,
"activities": self.activities_list,
@ -119,6 +118,25 @@ class StateManager:
self.arq_irs_sessions[session.id] = session
return True
def check_if_running_arq_session(self, irs=False):
sessions = self.arq_irs_sessions if irs else self.arq_iss_sessions
for session_id in sessions:
# do a session cleanup of outdated sessions before
if sessions[session_id].is_session_outdated():
print(f"session cleanup.....{session_id}")
if irs:
self.remove_arq_irs_session(session_id)
else:
self.remove_arq_iss_session(session_id)
# check if ongoing sessions available
if sessions[session_id].state.name not in ['ENDED', 'ABORTED', 'FAILED']:
print(f"[State Manager] running session...[{session_id}]")
return True
return False
def get_arq_iss_session(self, id):
if id not in self.arq_iss_sessions:
#raise RuntimeError(f"ARQ ISS Session '{id}' not found!")
@ -134,14 +152,12 @@ class StateManager:
return self.arq_irs_sessions[id]
def remove_arq_iss_session(self, id):
if id not in self.arq_iss_sessions:
raise RuntimeError(f"ARQ ISS Session '{id}' not found!")
del self.arq_iss_sessions[id]
if id in self.arq_iss_sessions:
del self.arq_iss_sessions[id]
def remove_arq_irs_session(self, id):
if id not in self.arq_irs_sessions:
raise RuntimeError(f"ARQ ISS Session '{id}' not found!")
del self.arq_irs_sessions[id]
if id in self.arq_irs_sessions:
del self.arq_irs_sessions[id]
def add_activity(self, activity_data):
# Generate a random 8-byte string as hex
@ -177,3 +193,12 @@ class StateManager:
else:
self.channel_busy_condition_codec2 = threading.Event()
self.calculate_channel_busy_state()
def get_radio_status(self):
return {
"radio_status": self.radio_status,
"radio_frequency": self.radio_frequency,
"radio_mode": self.radio_mode,
"radio_rf_level": self.radio_rf_level,
"s_meter_strength": self.s_meter_strength,
}

View file

@ -1,4 +1,5 @@
import sys
import time
sys.path.append('modem')
import unittest
@ -17,7 +18,7 @@ import numpy as np
from event_manager import EventManager
from data_frame_factory import DataFrameFactory
import codec2
import arq_session_irs
class TestModem:
def __init__(self, event_q):
self.data_queue_received = queue.Queue()
@ -129,12 +130,13 @@ class TestARQSession(unittest.TestCase):
self.establishChannels()
params = {
'dxcall': "DJ2LS-3",
'dxcall': "XX1XXX-1",
'data': base64.b64encode(bytes("Hello world!", encoding="utf-8")),
}
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
cmd.run(self.iss_event_queue, self.iss_modem)
self.waitAndCloseChannels()
del cmd
def DisabledtestARQSessionLargePayload(self):
# set Packet Error Rate (PER) / frame loss probability
@ -142,13 +144,14 @@ class TestARQSession(unittest.TestCase):
self.establishChannels()
params = {
'dxcall': "DJ2LS-3",
'dxcall': "XX1XXX-1",
'data': base64.b64encode(np.random.bytes(1000)),
}
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
cmd.run(self.iss_event_queue, self.iss_modem)
self.waitAndCloseChannels()
del cmd
def testARQSessionAbortTransmissionISS(self):
# set Packet Error Rate (PER) / frame loss probability
@ -156,7 +159,7 @@ class TestARQSession(unittest.TestCase):
self.establishChannels()
params = {
'dxcall': "DJ2LS-3",
'dxcall': "XX1XXX-1",
'data': base64.b64encode(np.random.bytes(100)),
}
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
@ -167,6 +170,7 @@ class TestARQSession(unittest.TestCase):
self.iss_state_manager.arq_iss_sessions[id].abort_transmission()
self.waitAndCloseChannels()
del cmd
def testARQSessionAbortTransmissionIRS(self):
# set Packet Error Rate (PER) / frame loss probability
@ -174,7 +178,7 @@ class TestARQSession(unittest.TestCase):
self.establishChannels()
params = {
'dxcall': "DJ2LS-3",
'dxcall': "XX1XXX-1",
'data': base64.b64encode(np.random.bytes(100)),
}
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
@ -185,6 +189,42 @@ class TestARQSession(unittest.TestCase):
self.irs_state_manager.arq_irs_sessions[id].abort_transmission()
self.waitAndCloseChannels()
del cmd
def testSessionCleanupISS(self):
params = {
'dxcall': "XX1XXX-1",
'data': base64.b64encode(np.random.bytes(100)),
}
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
cmd.run(self.iss_event_queue, self.iss_modem)
for session_id in self.iss_state_manager.arq_iss_sessions:
session = self.iss_state_manager.arq_iss_sessions[session_id]
ISS_States = session.state_enum
session.state = ISS_States.FAILED
session.session_ended = time.time() - 1000
if session.is_session_outdated():
self.logger.info(f"session [{session_id}] outdated - deleting it")
self.iss_state_manager.remove_arq_iss_session(session_id)
break
del cmd
def testSessionCleanupIRS(self):
session = arq_session_irs.ARQSessionIRS(self.config,
self.irs_modem,
'AA1AAA-1',
random.randint(0, 255))
self.irs_state_manager.register_arq_irs_session(session)
for session_id in self.irs_state_manager.arq_irs_sessions:
session = self.irs_state_manager.arq_irs_sessions[session_id]
irs_States = session.state_enum
session.state = irs_States.FAILED
session.session_ended = time.time() - 1000
if session.is_session_outdated():
self.logger.info(f"session [{session_id}] outdated - deleting it")
self.irs_state_manager.remove_arq_irs_session(session_id)
break
if __name__ == '__main__':
unittest.main()

View file

@ -12,7 +12,7 @@ from command_ping import PingCommand
from command_cq import CQCommand
import modem
import frame_handler
from radio_manager import RadioManager
class TestProtocols(unittest.TestCase):
@ -27,9 +27,10 @@ class TestProtocols(unittest.TestCase):
cls.event_queue = queue.Queue()
cls.event_manager = EventManager([cls.event_queue])
cls.radio_manager = RadioManager(cls.config, cls.state_manager, cls.event_manager)
cls.modem_transmit_queue = queue.Queue()
cls.modem = modem.RF(cls.config, cls.event_queue, queue.Queue(), queue.Queue(), cls.state_manager)
cls.modem = modem.RF(cls.config, cls.event_queue, queue.Queue(), queue.Queue(), cls.state_manager, cls.radio_manager)
modem.TESTMODE = True
frame_handler.TESTMODE = True

View file

@ -13,7 +13,8 @@ class TestIntegration(unittest.TestCase):
@classmethod
def setUpClass(cls):
cmd = "flask --app modem/server run"
#cmd = "flask --app modem/server run"
cmd = "python3 modem/server.py"
my_env = os.environ.copy()
my_env["FREEDATA_CONFIG"] = "modem/config.ini.example"
cls.process = Popen(shlex.split(cmd), stdin=PIPE, env=my_env)

View file

@ -0,0 +1,5 @@
REM Place this batch file in FreeData/modem and then run it
REM ie. c:\FD-Src\modem
python -m pip install -r ..\requirements.txt
pause

10
tools/Modem-Launch.bat Normal file
View file

@ -0,0 +1,10 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\tnc
REM Set environment variable to let modem know where to find config, change if you need to specify a different config
set FREEDATA_CONFIG=.\config.ini
REM launch modem
flask --app server run
pause

View file

@ -0,0 +1,6 @@
@echo off
REM PLace in modem directory and run to retrieve list of audio devices; you'll need the CRC for the config.ini
python ..\tools\list_audio_devices.py
pause

View file

@ -1,5 +1,8 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\gui_vue
@echo off
REM Place this batch file in FreeData/gui and then run it
REM ie. c:\FD-Src\gui
echo Install requirements for GUI...
call npm install
pause

View file

@ -1,5 +1,5 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\gui_vue
REM Place this batch file in FreeData/gui and then run it
REM ie. c:\FD-Src\gui
call npm start
pause

View file

@ -1,5 +1,8 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\gui_vue
@echo off
REM Place this batch file in FreeData/gui and then run it
REM ie. c:\FD-Src\gui
echo Check for and install updated requirements
call npm update
pause

View file

@ -0,0 +1,5 @@
REM Place this batch file in FreeData/modem and then run it
REM ie. c:\FD-Src\modem
python -m pip install -r ..\requirements.txt
pause

View file

@ -0,0 +1,10 @@
REM Place this batch file in FreeData/modem and then run it
REM ie. c:\FD-Src\modem
REM Set environment variable to let modem know where to find config, change if you need to specify a different config
set FREEDATA_CONFIG=.\config.ini
REM launch modem
python server.py
pause

View file

@ -0,0 +1,6 @@
@echo off
REM PLace in modem directory and run to retrieve list of audio devices; you'll need the CRC for the config.ini
python ..\tools\list_audio_devices.py
pause

View file

@ -1,5 +0,0 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\tnc
python -m pip install -r ..\requirements.txt
pause

View file

@ -1,5 +0,0 @@
REM Place this batch file in FreeData/tnc and then run it
REM ie. c:\FD-Src\tnc
python daemon.py
pause

View file

@ -1,6 +1,10 @@
@echo off
REM This will copy the helper batch files to the approriate places for you
copy GUI* ..\..\gui_vue\
copy TNC* ..\..\tnc\
echo Copying GUI scripts to GUI directory
copy GUI* ..\..\gui\
echo Copying Modem scripts to Modem directory
copy MODEM* ..\..\modem\
pause

View file

@ -1,2 +1,2 @@
FREEDATA_CONFIG=modem/config.ini flask --app modem/server run
# FREEDATA_CONFIG=modem/config.ini flask --app modem/server run
FREEDATA_CONFIG=modem/config.ini python3 modem/server.py