mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge branch 'develop' into dependabot/npm_and_yarn/gui/develop/typescript-5.3.3
This commit is contained in:
commit
318a3498bb
72 changed files with 1654 additions and 1255 deletions
49
.github/workflows/build_server.yml
vendored
49
.github/workflows/build_server.yml
vendored
|
@ -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')
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
"port": "5000",
|
||||
"spectrum": "waterfall",
|
||||
"wf_theme": 2,
|
||||
"theme": "default_light",
|
||||
"high_graphics": true,
|
||||
"update_channel": "alpha",
|
||||
"enable_sys_notification": false
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
>
|
||||
<div
|
||||
class="progress rounded-0 rounded-bottom"
|
||||
hidden
|
||||
:style="{ height: '10px' }"
|
||||
v-bind:class="{
|
||||
'bg-danger': message.status == 'failed',
|
||||
|
|
|
@ -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-")) {
|
||||
|
||||
|
|
|
@ -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 <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>
|
||||
</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>
|
||||
<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',
|
||||
}"
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
<strong>Audio</strong>
|
||||
</div>
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<div class="container-wide">
|
||||
|
|
|
@ -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',
|
||||
}"
|
||||
|
|
|
@ -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"
|
||||
|
|
36
gui/src/components/grid/grid_beacon.vue
Normal file
36
gui/src/components/grid/grid_beacon.vue
Normal 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
|
||||
<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>
|
30
gui/src/components/grid/grid_mycall small.vue
Normal file
30
gui/src/components/grid/grid_mycall small.vue
Normal 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>
|
|
@ -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>
|
||||
|
|
91
gui/src/components/grid/grid_scatter.vue
Normal file
91
gui/src/components/grid/grid_scatter.vue
Normal 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>
|
|
@ -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>
|
||||
|
|
18
gui/src/components/grid/grid_tune.vue
Normal file
18
gui/src/components/grid/grid_tune.vue
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
}"
|
||||
|
|
|
@ -368,7 +368,7 @@ export default {
|
|||
width: 100%;
|
||||
height: 200px;
|
||||
"
|
||||
:class="settings.local.high_graphics == true ? 'force-gpu' : ''"
|
||||
class="force-gpu'"
|
||||
></canvas>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
>
|
||||
<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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
34
gui/src/components/settings_tci.vue
Normal file
34
gui/src/components/settings_tci.vue
Normal 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>
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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 ||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -97,41 +97,3 @@ class DISPATCHER():
|
|||
if data[:1] == FR_TYPE.ARQ_SESSION_OPEN:
|
||||
return data[13:14]
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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'']
|
||||
|
||||
|
||||
|
|
|
@ -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
59
modem/radio_manager.py
Normal 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()
|
480
modem/rigctld.py
480
modem/rigctld.py
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
5
tools/Modem-Install-Requrements.bat
Normal file
5
tools/Modem-Install-Requrements.bat
Normal 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
10
tools/Modem-Launch.bat
Normal 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
|
6
tools/Modem-list-audio-devs.bat
Normal file
6
tools/Modem-list-audio-devs.bat
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
5
tools/Windows/Modem-Install-Requrements.bat
Normal file
5
tools/Windows/Modem-Install-Requrements.bat
Normal 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/Windows/Modem-Launch.bat
Normal file
10
tools/Windows/Modem-Launch.bat
Normal 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
|
6
tools/Windows/Modem-list-audio-devs.bat
Normal file
6
tools/Windows/Modem-list-audio-devs.bat
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue