mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
commit
1bb18e1e98
BIN
gui/public/dj2ls.png
Normal file
BIN
gui/public/dj2ls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
BIN
gui/public/ei7ig.jpeg
Normal file
BIN
gui/public/ei7ig.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
gui/public/kt4wo.png
Normal file
BIN
gui/public/kt4wo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 342 KiB |
3
gui/public/person-fill.svg
Normal file
3
gui/public/person-fill.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
|
||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 225 B |
BIN
gui/public/vk5dgr.jpeg
Normal file
BIN
gui/public/vk5dgr.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
|
@ -45,10 +45,14 @@ function chatSelected(callsign) {
|
|||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="list-group m-0 p-0" id="chat-list-tab" role="chat-tablist">
|
||||
<div
|
||||
class="list-group bg-body-tertiary m-0 p-1"
|
||||
id="chat-list-tab"
|
||||
role="chat-tablist"
|
||||
>
|
||||
<template v-for="(item, key) in chat.callsign_list" :key="item.dxcallsign">
|
||||
<a
|
||||
class="list-group-item list-group-item-action border-0 border-bottom rounded-0"
|
||||
class="list-group-item list-group-item-action list-group-item-dark rounded-2 border-0 mb-2"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-chat-list-${item}`"
|
||||
data-bs-toggle="list"
|
||||
|
@ -59,7 +63,7 @@ function chatSelected(callsign) {
|
|||
>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
{{ item }}
|
||||
<strong>{{ item }}</strong>
|
||||
<span
|
||||
class="badge rounded-pill bg-danger"
|
||||
v-if="getNewMessagesByDXCallsign(item)[1] > 0"
|
||||
|
|
|
@ -43,7 +43,8 @@
|
|||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card bg-secondary text-white">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
|
@ -46,15 +46,23 @@
|
|||
<p class="card-text">{{ message.msg }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-primary border-top-0">
|
||||
<div class="card-footer p-0 bg-secondary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 border-top-0" v-if="message.percent < 100">
|
||||
<div
|
||||
class="card-footer p-0 border-top-0"
|
||||
v-if="message.percent < 100 || message.status === 'failed'"
|
||||
>
|
||||
<div
|
||||
class="progress bg-secondary rounded-0 rounded-bottom"
|
||||
class="progress rounded-0 rounded-bottom"
|
||||
:style="{ height: '10px' }"
|
||||
v-bind:class="{
|
||||
'bg-danger': message.status == 'failed',
|
||||
'bg-primary': message.status == 'transmitting',
|
||||
'bg-secondary': message.status == 'transmitted',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
|
@ -64,7 +72,8 @@
|
|||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
227
gui/src/components/infoScreen.vue
Normal file
227
gui/src/components/infoScreen.vue
Normal file
|
@ -0,0 +1,227 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
function openWebExternal(url) {
|
||||
open(url);
|
||||
}
|
||||
const cards = ref([
|
||||
{
|
||||
titleName: "Simon",
|
||||
titleCall: "DJ2LS",
|
||||
role: "Founder & Core developer",
|
||||
imgSrc: "dj2ls.png",
|
||||
},
|
||||
{
|
||||
titleName: "Alan",
|
||||
titleCall: "N1QM",
|
||||
role: "developer",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "Stefan",
|
||||
titleCall: "DK5SM",
|
||||
role: "tester",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "Wolfgang",
|
||||
titleCall: "DL4IAZ",
|
||||
role: "supporter",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "David",
|
||||
titleCall: "VK5DGR",
|
||||
role: "codec2 founder",
|
||||
imgSrc: "vk5dgr.jpeg",
|
||||
},
|
||||
{
|
||||
titleName: "John",
|
||||
titleCall: "EI7IG",
|
||||
role: "tester",
|
||||
imgSrc: "ei7ig.jpeg",
|
||||
},
|
||||
{
|
||||
titleName: "Paul",
|
||||
titleCall: "N2KIQ",
|
||||
role: "developer",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "Trip",
|
||||
titleCall: "KT4WO",
|
||||
role: "tester",
|
||||
imgSrc: "kt4wo.png",
|
||||
},
|
||||
{
|
||||
titleName: "Manuel",
|
||||
titleCall: "DF7MH",
|
||||
role: "tester",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "Darren",
|
||||
titleCall: "G0HWW",
|
||||
role: "tester",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
{
|
||||
titleName: "Kai",
|
||||
titleCall: "LA3QMA",
|
||||
role: "developer",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
]);
|
||||
|
||||
// Shuffle cards
|
||||
function shuffleCards() {
|
||||
cards.value = cards.value.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
onMounted(shuffleCards);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 class="m-2">
|
||||
<span class="badge bg-secondary">FreeDATA: {{ version }}</span>
|
||||
<span class="ms-2 badge bg-secondary"
|
||||
>Modem: {{ state.modem_version }}</span
|
||||
>
|
||||
</h3>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mt-2">
|
||||
<hr />
|
||||
<h6>Important URLs</h6>
|
||||
|
||||
<div
|
||||
class="btn-toolbar mx-auto"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm bi bi-geo-alt btn-secondary me-2"
|
||||
id="openExplorer"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="openWebExternal('https://explorer.freedata.app')"
|
||||
>
|
||||
Explorer map
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-2 bi bi-graph-up"
|
||||
id="btnStats"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="openWebExternal('https://statistics.freedata.app/')"
|
||||
>
|
||||
Statistics
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-bookmarks me-2"
|
||||
id="fdWww"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="FreeDATA website"
|
||||
role="button"
|
||||
@click="openWebExternal('https://freedata.app')"
|
||||
>
|
||||
Website
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-github me-2"
|
||||
id="ghUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Github"
|
||||
role="button"
|
||||
@click="openWebExternal('https://github.com/dj2ls/freedata')"
|
||||
>
|
||||
Github
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-wikipedia me-2"
|
||||
id="wikiUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Wiki"
|
||||
role="button"
|
||||
@click="openWebExternal('https://wiki.freedata.app')"
|
||||
>
|
||||
Wiki
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-discord"
|
||||
id="discordUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Discord"
|
||||
role="button"
|
||||
@click="openWebExternal('https://discord.freedata.app')"
|
||||
>
|
||||
Discord
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<hr />
|
||||
<h6>Special thanks to</h6>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-nowrap overflow-x-auto vh-100">
|
||||
<div class="row row-cols-1 row-cols-md-6 g-4 h-100">
|
||||
<div class="d-inline-block" v-for="card in cards" :key="card.titleName">
|
||||
<div class="col">
|
||||
<div
|
||||
class="card border-dark mb-3 ms-1 me-1"
|
||||
style="max-width: 15rem"
|
||||
>
|
||||
<img :src="card.imgSrc" class="card-img-top grayscale" />
|
||||
<div class="card-body">
|
||||
<p class="card-text text-center">{{ card.role }}</p>
|
||||
</div>
|
||||
<div class="card-footer text-body-secondary text-center">
|
||||
<strong>{{ card.titleCall }}</strong>
|
||||
</div>
|
||||
<div class="card-footer text-body-secondary text-center">
|
||||
<strong>{{ card.titleName }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.grayscale {
|
||||
filter: grayscale(100%);
|
||||
transition: filter 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.grayscale:hover {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
</style>
|
|
@ -3,9 +3,6 @@ import { setActivePinia } from "pinia";
|
|||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import main_modals from "./main_modals.vue";
|
||||
import main_top_navbar from "./main_top_navbar.vue";
|
||||
import main_audio from "./main_audio.vue";
|
||||
|
@ -22,18 +19,14 @@ import main_active_heard_stations from "./main_active_heard_stations.vue";
|
|||
import main_active_audio_level from "./main_active_audio_level.vue";
|
||||
|
||||
import chat from "./chat.vue";
|
||||
import infoScreen from "./infoScreen.vue";
|
||||
|
||||
import { stopTransmission } from "../js/sock.js";
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
function stopAllTransmissions() {
|
||||
console.log("stopping transmissions");
|
||||
stopTransmission();
|
||||
}
|
||||
function openWebExternal(url) {
|
||||
open(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -63,7 +56,7 @@ function openWebExternal(url) {
|
|||
style="margin-top: 100px"
|
||||
>
|
||||
<a
|
||||
class="list-group-item list-group-item-action active"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2 active"
|
||||
id="list-modem-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-modem"
|
||||
|
@ -73,7 +66,7 @@ function openWebExternal(url) {
|
|||
><i class="bi bi-house-door-fill h3"></i
|
||||
></a>
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-chat-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chat"
|
||||
|
@ -84,7 +77,7 @@ function openWebExternal(url) {
|
|||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-mesh-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-mesh"
|
||||
|
@ -94,7 +87,7 @@ function openWebExternal(url) {
|
|||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action mt-2 border"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border border-0 rounded-3 mb-2"
|
||||
id="list-info-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-info"
|
||||
|
@ -105,7 +98,7 @@ function openWebExternal(url) {
|
|||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-logger-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-logger"
|
||||
|
@ -115,7 +108,7 @@ function openWebExternal(url) {
|
|||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action rounded-bottom"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-settings-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-settings"
|
||||
|
@ -354,109 +347,7 @@ function openWebExternal(url) {
|
|||
role="tabpanel"
|
||||
aria-labelledby="list-info-list"
|
||||
>
|
||||
<h1 class="modal-title fs-5" id="aboutModalLabel">
|
||||
FreeDATA - {{ version }}
|
||||
</h1>
|
||||
|
||||
<h4 class="fs-5">modem version - {{ state.modem_version }}</h4>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mt-2">
|
||||
<div
|
||||
class="btn-toolbar mx-auto"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm bi bi-geo-alt btn-secondary me-2"
|
||||
id="openExplorer"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="openWebExternal('https://explorer.freedata.app')"
|
||||
>
|
||||
Explorer map
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-2 bi bi-graph-up"
|
||||
id="btnStats"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="
|
||||
openWebExternal('https://statistics.freedata.app/')
|
||||
"
|
||||
>
|
||||
Statistics
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-bookmarks me-2"
|
||||
id="fdWww"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="FreeDATA website"
|
||||
role="button"
|
||||
@click="openWebExternal('https://freedata.app')"
|
||||
>
|
||||
Website
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-github me-2"
|
||||
id="ghUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Github"
|
||||
role="button"
|
||||
@click="
|
||||
openWebExternal('https://github.com/dj2ls/freedata')
|
||||
"
|
||||
>
|
||||
Github
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-wikipedia me-2"
|
||||
id="wikiUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Wiki"
|
||||
role="button"
|
||||
@click="openWebExternal('https://wiki.freedata.app')"
|
||||
>
|
||||
Wiki
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-discord"
|
||||
id="discordUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Discord"
|
||||
role="button"
|
||||
@click="openWebExternal('https://discord.freedata.app')"
|
||||
>
|
||||
Discord
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<h6>Special thanks to</h6>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4" id="divContrib"></div>
|
||||
<div class="col-4" id="divContrib2"></div>
|
||||
<div class="col-4" id="divContrib3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<infoScreen />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
|
|
|
@ -71,29 +71,45 @@ function getMaidenheadDistance(dxGrid) {
|
|||
<th scope="col" id="thTime">
|
||||
<i id="hslSort" class="bi bi-sort-up"></i>Time
|
||||
</th>
|
||||
<th scope="col" id="thFreq">Frequency</th>
|
||||
<th> </th>
|
||||
<th scope="col" id="thFreq">Freq</th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
<th scope="col" id="thDxgrid">DXGrid</th>
|
||||
<th scope="col" id="thDist">Distance</th>
|
||||
<th scope="col" id="thDxgrid">Grid</th>
|
||||
<th scope="col" id="thDist">Dist</th>
|
||||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR (rx/dx)</th>
|
||||
<th scope="col" id="thSnr">SNR</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="heardstations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.timestamp">
|
||||
<td>{{ getDateTime(item.timestamp) }}</td>
|
||||
<td>{{ item.frequency }}</td>
|
||||
<td> </td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{
|
||||
getDateTime(item.timestamp)
|
||||
}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary"
|
||||
>{{ item.frequency / 1000 }} kHz</span
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.dxcallsign }}</span>
|
||||
</td>
|
||||
<td>{{ item.dxgrid }}</td>
|
||||
<td>{{ getMaidenheadDistance(item.dxgrid) }} km</td>
|
||||
<td>{{ item.datatype }}</td>
|
||||
<td>{{ item.snr }}</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.dxgrid }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary"
|
||||
>{{ getMaidenheadDistance(item.dxgrid) }} km</span
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.datatype }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.snr }}</span>
|
||||
</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -8,7 +8,12 @@ const state = useStateStore(pinia);
|
|||
|
||||
import { set_frequency, set_mode, set_rf_level } from "../js/sock.js";
|
||||
|
||||
function set_hamlib_frequency() {
|
||||
function updateFrequencyAndApply(frequency) {
|
||||
state.new_frequency = frequency;
|
||||
set_frequency(state.new_frequency);
|
||||
}
|
||||
|
||||
function set_hamlib_frequency_manually() {
|
||||
set_frequency(state.new_frequency);
|
||||
}
|
||||
|
||||
|
@ -58,50 +63,156 @@ function set_hamlib_rf_level() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="input-group bottom-0 m-0">
|
||||
<div class="input-group input-group-sm bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text">{{ state.frequency }} Hz</span>
|
||||
<span class="input-group-text">QSY</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="state.new_frequency"
|
||||
style="max-width: 8rem"
|
||||
pattern="[0-9]*"
|
||||
list="frequencyDataList"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
/>
|
||||
|
||||
<datalist id="frequencyDataList">
|
||||
<option selected value="7053000">40m | USB | EU, US</option>
|
||||
<option value="14093000">20m | USB | EU, US</option>
|
||||
<option value="21093000">15m | USB | EU, US</option>
|
||||
<option value="24908000">12m | USB | EU, US</option>
|
||||
<option value="27245000">11m | USB | Ch 25</option>
|
||||
<option value="27265000">11m | USB | Ch 26</option>
|
||||
<option value="28093000">10m | USB | EU, US</option>
|
||||
<option value="50308000">6m | USB | US</option>
|
||||
<option value="50616000">6m | USB | EU, US</option>
|
||||
</datalist>
|
||||
<!-- Dropdown Button -->
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
type="button"
|
||||
@click="set_hamlib_frequency"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownMenuButton"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Apply
|
||||
Select Frequency
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
<li>
|
||||
<div class="input-group p-1">
|
||||
<span class="input-group-text">frequency</span>
|
||||
|
||||
<input
|
||||
v-model="state.new_frequency"
|
||||
style="max-width: 8rem"
|
||||
pattern="[0-9]*"
|
||||
type="text"
|
||||
class="form-control form-control-sm"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
placeholder="Type frequency..."
|
||||
aria-label="Frequency"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
type="button"
|
||||
@click="set_hamlib_frequency_manually"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-check-square"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Dropdown Divider -->
|
||||
<li><hr class="dropdown-divider" /></li>
|
||||
<!-- Dropdown Items -->
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(50616000)"
|
||||
><strong>50616 kHz</strong>
|
||||
<span class="badge bg-secondary">6m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(50308000)"
|
||||
><strong>50308 kHz</strong>
|
||||
<span class="badge bg-secondary">6m | USB</span>
|
||||
<span class="badge bg-info">US</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(28093000)"
|
||||
><strong>28093 kHz</strong>
|
||||
<span class="badge bg-secondary">10m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(27265000)"
|
||||
><strong>27265 kHz</strong>
|
||||
<span class="badge bg-secondary">11m | USB</span>
|
||||
<span class="badge bg-dark">Ch 26</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(27245000)"
|
||||
><strong>27245 kHz</strong>
|
||||
<span class="badge bg-secondary">11m | USB</span>
|
||||
<span class="badge bg-dark">Ch 25</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(24908000)"
|
||||
><strong>24908 kHz</strong>
|
||||
<span class="badge bg-secondary">12m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(21093000)"
|
||||
><strong>21093 kHz</strong>
|
||||
<span class="badge bg-secondary">15m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(14093000)"
|
||||
><strong>14093 kHz</strong>
|
||||
<span class="badge bg-secondary">20m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
@click="updateFrequencyAndApply(7053000)"
|
||||
><strong>7053 kHz</strong>
|
||||
<span class="badge bg-secondary">40m | USB</span>
|
||||
<span class="badge bg-info">EU | US</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
|
@ -123,7 +234,7 @@ function set_hamlib_rf_level() {
|
|||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
|
|
|
@ -191,15 +191,15 @@ export default {
|
|||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<div class="btn-group" role="group">
|
||||
<div class="col-11 p-0">
|
||||
<div class="btn-group h-100" role="group">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
class="list-group bg-body-tertiary list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-waterfall-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-waterfall"
|
||||
|
@ -210,7 +210,7 @@ export default {
|
|||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-scatter-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-scatter"
|
||||
|
@ -221,7 +221,7 @@ export default {
|
|||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-chart-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chart"
|
||||
|
|
|
@ -18,14 +18,16 @@ const state = useStateStore(pinia);
|
|||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.ptt_state === 'True',
|
||||
'bg-secondary': state.ptt_state === 'False',
|
||||
'btn-danger': state.ptt_state === 'True',
|
||||
'btn-secondary': state.ptt_state === 'False',
|
||||
}"
|
||||
id="ptt_state"
|
||||
type="button"
|
||||
title="Rig PTT state"
|
||||
style="pointer-events: auto"
|
||||
disabled
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-title="PTT trigger state"
|
||||
>
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
@ -39,10 +41,10 @@ const state = useStateStore(pinia);
|
|||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.busy_state === 'BUSY',
|
||||
'bg-secondary': state.busy_state === 'IDLE',
|
||||
'btn-danger': state.busy_state === 'BUSY',
|
||||
'btn-secondary': state.busy_state === 'IDLE',
|
||||
}"
|
||||
title="Modem state"
|
||||
data-bs-title="Modem state"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
|
@ -58,12 +60,12 @@ const state = useStateStore(pinia);
|
|||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_session_state === 'disconnected',
|
||||
'bg-warning': state.arq_session_state === 'connected',
|
||||
'btn-secondary': state.arq_session_state === 'disconnected',
|
||||
'btn-warning': state.arq_session_state === 'connected',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
title="Session state"
|
||||
data-bs-title="Session state"
|
||||
>
|
||||
<i class="bi bi-arrow-left-right" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
@ -72,10 +74,13 @@ const state = useStateStore(pinia);
|
|||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_state"
|
||||
type="button"
|
||||
title="Data channel state"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="Data channel state"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_state === 'False',
|
||||
'bg-warning': state.arq_state === 'True',
|
||||
'btn-secondary': state.arq_state === 'False',
|
||||
'btn-warning': state.arq_state === 'True',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
|
@ -98,7 +103,7 @@ const state = useStateStore(pinia);
|
|||
-->
|
||||
|
||||
<button
|
||||
class="btn btn-sm disabled me-3"
|
||||
class="btn btn-sm btn-secondary disabled me-3"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
|
@ -109,7 +114,7 @@ const state = useStateStore(pinia);
|
|||
'btn-secondary': state.channel_busy === 'False',
|
||||
}"
|
||||
style="pointer-events: auto"
|
||||
title="Channel busy"
|
||||
data-bs-title="Channel busy"
|
||||
>
|
||||
<i class="bi bi-hourglass"></i>
|
||||
</button>
|
||||
|
@ -119,7 +124,10 @@ const state = useStateStore(pinia);
|
|||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
title="What's the frequency, Kenneth?"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="What's the frequency, Kenneth?"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
{{ parseInt(state.frequency) / 1000 }} KHz
|
||||
|
@ -142,6 +150,7 @@ const state = useStateStore(pinia);
|
|||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-titel="speed level"
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
|
@ -172,6 +181,7 @@ const state = useStateStore(pinia);
|
|||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="total bytes of transmission"
|
||||
>
|
||||
{{ state.arq_total_bytes }}
|
||||
</button>
|
||||
|
@ -184,7 +194,7 @@ const state = useStateStore(pinia);
|
|||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Current or last connected with station"
|
||||
data-bs-title="Current or last connected with station"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
@ -196,6 +206,7 @@ const state = useStateStore(pinia);
|
|||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="the dxcallsign of the connected station"
|
||||
>
|
||||
{{ state.dxcallsign }}
|
||||
</button>
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import {
|
||||
deleteChatByCallsign,
|
||||
getNewMessagesByDXCallsign,
|
||||
|
@ -23,7 +24,8 @@ function tuneAudio() {
|
|||
}
|
||||
|
||||
function set_audio_level() {
|
||||
setTxAudioLevel(state.audio_level);
|
||||
saveSettingsToFile();
|
||||
setTxAudioLevel(settings.tx_audio_level);
|
||||
}
|
||||
|
||||
function deleteChat() {
|
||||
|
@ -1194,7 +1196,7 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TX Level</span>
|
||||
<span class="input-group-text">{{ state.audio_level }}</span>
|
||||
<span class="input-group-text">{{ settings.tx_audio_level }}</span>
|
||||
<span class="input-group-text w-75">
|
||||
<input
|
||||
type="range"
|
||||
|
@ -1204,7 +1206,7 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
step="1"
|
||||
id="audioLevelTX"
|
||||
@click="set_audio_level()"
|
||||
v-model="state.audio_level"
|
||||
v-model="settings.tx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,8 @@ function startStopRigctld() {
|
|||
break;
|
||||
case "running":
|
||||
stopRigctld();
|
||||
|
||||
// dirty hack for calling this command twice, otherwise modem won't stop rigctld from time to time
|
||||
stopRigctld();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
@ -73,12 +74,12 @@ alert("not yet implemented")
|
|||
|
||||
<div class="col-6">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
class="list-group bg-body-tertiary list-group-horizontal w-75"
|
||||
id="rig-control-list-tab"
|
||||
role="rig-control-tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
class="p-1 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-rig-control-none-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-none"
|
||||
|
@ -86,10 +87,10 @@ alert("not yet implemented")
|
|||
aria-controls="list-rig-control-none"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'disabled' }"
|
||||
@click="selectRadioControl()"
|
||||
>None/Vox</a
|
||||
>None</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
class="p-1 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-rig-control-rigctld-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-rigctld"
|
||||
|
@ -100,7 +101,7 @@ alert("not yet implemented")
|
|||
>Rigctld</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
class="p-1 list-group-item list-group-item-dark list-group-item-action"
|
||||
id="list-rig-control-tci-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-tci"
|
||||
|
@ -138,7 +139,8 @@ alert("not yet implemented")
|
|||
>
|
||||
<p class="small">
|
||||
Modem will not utilize rig control and features will be limited. While
|
||||
functional; it is recommended to configure hamlib.
|
||||
functional; it is recommended to configure hamlib. <br>
|
||||
Use this setting also for <strong> VOX </strong>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -103,26 +103,5 @@ function startStopModem() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false"
|
||||
title="View the received files. This is currently under development!">
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#receivedFilesSidebar" id="openReceivedFiles" type="button" > <strong>Files </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
<i class="bi bi-file-earmark-arrow-down-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false" title="Send files through HF. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openDataModule" data-bs-toggle="offcanvas" data-bs-target="#transmitFileSidebar" type="button" style="display: None;"> <strong>TX File </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="true"
|
||||
title="Settings and Info">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
--></nav>
|
||||
</nav>
|
||||
</template>
|
||||
|
|
|
@ -14,156 +14,164 @@ import settings_exp from "./settings_exp.vue";
|
|||
aria-labelledby="list-settings-list"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="badge text-bg-warning ms-3">
|
||||
<i class="bi bi-exclamation-triangle"></i> Please restart the modem
|
||||
after changing settings!
|
||||
<div class="badge text-bg-warning m-1">
|
||||
<h5>
|
||||
<i class="bi bi-exclamation-triangle"></i> Please restart the modem
|
||||
after changing settings <i class="bi bi-exclamation-triangle"></i>
|
||||
</h5>
|
||||
</div>
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</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"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
<div class="tab-content mt-1">
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</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"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
<div class="tab-content">
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -112,7 +112,6 @@ function saveSettings() {
|
|||
id="max_retry_attempts"
|
||||
@change="saveSettings"
|
||||
v-model="settings.max_retry_attempts"
|
||||
disabled
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
|
|
|
@ -202,9 +202,14 @@ export function newMessage(
|
|||
filename = chatFileName;
|
||||
} else {
|
||||
file = "";
|
||||
filetype = "text";
|
||||
filetype = "";
|
||||
filename = "";
|
||||
}
|
||||
|
||||
console.log(file);
|
||||
console.log(filetype);
|
||||
console.log(filename);
|
||||
|
||||
var file_checksum = ""; //crc32(file).toString(16).toUpperCase();
|
||||
var message_type = "transmit";
|
||||
var command = "msg";
|
||||
|
@ -536,6 +541,29 @@ export async function updateAllChat(cleanup) {
|
|||
} else {
|
||||
chat.unsorted_chat_list.push(item);
|
||||
chat.sorted_chat_list = sortChatList();
|
||||
|
||||
// check if message is expired
|
||||
let timeNow = Math.floor(Date.now() / 1000);
|
||||
let expireTimestamp = timeNow - 10 * 60;
|
||||
let isExpired = false;
|
||||
if (expireTimestamp >= item.timestamp) {
|
||||
isExpired = true;
|
||||
}
|
||||
if (item.status == "transmitting" && isExpired) {
|
||||
console.log("message expired - resetting status");
|
||||
console.log(item);
|
||||
databaseUpsert(item.uuid, "status", "failed");
|
||||
updateUnsortedChatListEntry(item.uuid, "status", "failed");
|
||||
databaseUpsert(item.uuid, "percent", 0);
|
||||
updateUnsortedChatListEntry(item.uuid, "percent", 0);
|
||||
}
|
||||
// lets update the message if it is failed. Then its always 0 percent
|
||||
if (item.status == "failed") {
|
||||
databaseUpsert(item.uuid, "percent", 0);
|
||||
updateUnsortedChatListEntry(item.uuid, "percent", 0);
|
||||
databaseUpsert(item.uuid, "bytesperminute", 0);
|
||||
updateUnsortedChatListEntry(item.uuid, "bytesperminute", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -861,7 +889,7 @@ export function requestMessageInfo(id) {
|
|||
// id and uuid are the same
|
||||
var data = getFromUnsortedChatListByUUID(id);
|
||||
chat.selectedMessageObject = data;
|
||||
|
||||
console.log(data);
|
||||
if (
|
||||
typeof data["speed_list"] !== "undefined" &&
|
||||
data["speed_list"].length > 0
|
||||
|
@ -986,14 +1014,28 @@ async function checkForWaitingMessages(dxcall) {
|
|||
|
||||
console.log(message);
|
||||
displayToast("info", "bi bi-info-circle", message, 5000);
|
||||
|
||||
console.log(result);
|
||||
// @ts-expect-error
|
||||
console.log(result.docs);
|
||||
// @ts-expect-error
|
||||
console.log(result.docs.length);
|
||||
// handle result
|
||||
// @ts-expect-error
|
||||
if (result.docs.length > 0) {
|
||||
// only want to process the first available item object, then return
|
||||
// this ensures, we are only sending one message at once
|
||||
// @ts-expect-error
|
||||
console.log(result.docs[0]);
|
||||
console.log(
|
||||
"attempt: " +
|
||||
// @ts-expect-error
|
||||
result.docs[0].attempt +
|
||||
"/" +
|
||||
settings.max_retry_attempts,
|
||||
);
|
||||
// @ts-expect-error
|
||||
if (result.docs[0].attempt < settings.max_retry_attempts) {
|
||||
console.log("repeating message...");
|
||||
// @ts-expect-error
|
||||
repeatMessageTransmission(result.docs[0].uuid);
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ export function startModem() {
|
|||
low_bandwidth_mode: settings.low_bandwidth_mode,
|
||||
tuning_range_fmin: settings.tuning_range_fmin,
|
||||
tuning_range_fmax: settings.tuning_range_fmax,
|
||||
//tx_audio_level: settings.tx_audio_level,
|
||||
tx_audio_level: settings.tx_audio_level,
|
||||
respond_to_cq: settings.respond_to_cq,
|
||||
rx_buffer_size: settings.rx_buffer_size,
|
||||
enable_explorer: settings.enable_explorer,
|
||||
|
|
|
@ -55,6 +55,7 @@ const configDefaultSettings =
|
|||
"daemon_port": 3001,\
|
||||
"rx_audio" : "",\
|
||||
"tx_audio" : "",\
|
||||
"tx_audio_level" : 100,\
|
||||
"mycall": "AA0AA-0",\
|
||||
"myssid": "0",\
|
||||
"mygrid": "JN40aa",\
|
||||
|
|
|
@ -30,7 +30,7 @@ const split_char = "0;1;";
|
|||
|
||||
// global to keep track of Modem connection error emissions
|
||||
var modemShowConnectStateError = 1;
|
||||
|
||||
var setTxAudioLevelOnce = true;
|
||||
// network connection Timeout
|
||||
setTimeout(connectModem, 2000);
|
||||
|
||||
|
@ -171,6 +171,20 @@ client.on("data", function (socketdata) {
|
|||
stateStore.arq_state = data["arq_state"];
|
||||
stateStore.mode = data["mode"];
|
||||
stateStore.bandwidth = data["bandwidth"];
|
||||
stateStore.tx_audio_level = data["audio_level"];
|
||||
// if audio level is different from config one, send new audio level to modem
|
||||
//console.log(parseInt(stateStore.tx_audio_level))
|
||||
//console.log(parseInt(settings.tx_audio_level))
|
||||
if (
|
||||
parseInt(stateStore.tx_audio_level) !==
|
||||
parseInt(settings.tx_audio_level) &&
|
||||
setTxAudioLevelOnce === true
|
||||
) {
|
||||
setTxAudioLevelOnce = false;
|
||||
console.log(setTxAudioLevelOnce);
|
||||
setTxAudioLevel(settings.tx_audio_level);
|
||||
}
|
||||
|
||||
stateStore.dbfs_level = data["audio_dbfs"];
|
||||
stateStore.ptt_state = data["ptt_state"];
|
||||
stateStore.speed_level = data["speed_level"];
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { loadSettings } from "./js/settingsHandler";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
// Import all of Bootstrap's JS
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import { Chart, Filler } from "chart.js";
|
||||
// Register the Filler plugin globally
|
||||
Chart.register(Filler);
|
||||
|
||||
// Import our custom CSS
|
||||
//import './scss/styles.scss'
|
||||
|
@ -19,8 +14,22 @@ const app = createApp(App);
|
|||
//.mount('#app').$nextTick(() => postMessage({ payload: 'removeLoading' }, '*'))
|
||||
const pinia = createPinia();
|
||||
app.mount("#app");
|
||||
|
||||
app.use(pinia);
|
||||
|
||||
// Import all of Bootstrap's JS
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
const tooltipTriggerList = document.querySelectorAll(
|
||||
'[data-bs-toggle="tooltip"]',
|
||||
);
|
||||
// @ts-expect-error
|
||||
const tooltipList = [...tooltipTriggerList].map(
|
||||
(tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl),
|
||||
);
|
||||
|
||||
loadSettings();
|
||||
|
||||
//import './js/settingsHandler.js'
|
||||
|
|
|
@ -5,6 +5,7 @@ export const useSettingsStore = defineStore("settingsStore", () => {
|
|||
// audio
|
||||
var tx_audio = ref();
|
||||
var rx_audio = ref();
|
||||
var tx_audio_level = ref();
|
||||
|
||||
// network
|
||||
var modem_host = ref("127.0.0.1");
|
||||
|
@ -157,6 +158,7 @@ export const useSettingsStore = defineStore("settingsStore", () => {
|
|||
enable_mesh_features: enable_mesh_features.value,
|
||||
tx_audio: tx_audio.value,
|
||||
rx_audio: rx_audio.value,
|
||||
tx_audio_level: tx_audio_level.value,
|
||||
};
|
||||
|
||||
return config_export;
|
||||
|
@ -224,5 +226,6 @@ export const useSettingsStore = defineStore("settingsStore", () => {
|
|||
rx_audio,
|
||||
getSerialDevices,
|
||||
serial_devices,
|
||||
tx_audio_level,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -40,7 +40,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
var audio_recording = ref("");
|
||||
|
||||
var hamlib_status = ref("");
|
||||
var audio_level = ref("");
|
||||
var tx_audio_level = ref("");
|
||||
var alc = ref("");
|
||||
|
||||
var is_codec2_traffic = ref("");
|
||||
|
@ -158,7 +158,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
arq_total_bytes,
|
||||
audio_recording,
|
||||
hamlib_status,
|
||||
audio_level,
|
||||
tx_audio_level,
|
||||
alc,
|
||||
updateTncState,
|
||||
arq_transmission_percent,
|
||||
|
|
|
@ -32,7 +32,6 @@ import structlog
|
|||
import ujson as json
|
||||
import config
|
||||
|
||||
|
||||
# signal handler for closing application
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
|
@ -77,6 +76,39 @@ class DAEMON:
|
|||
worker = threading.Thread(target=self.worker, name="WORKER", daemon=True)
|
||||
worker.start()
|
||||
|
||||
rigctld_watchdog_thread = threading.Thread(target=self.rigctld_watchdog, name="WORKER", daemon=True)
|
||||
rigctld_watchdog_thread.start()
|
||||
|
||||
|
||||
def rigctld_watchdog(self):
|
||||
"""
|
||||
Check for rigctld status
|
||||
Returns:
|
||||
|
||||
"""
|
||||
while True:
|
||||
threading.Event().wait(0.01)
|
||||
|
||||
# only continue, if we have a process object initialized
|
||||
if hasattr(Daemon.rigctldprocess, "returncode"):
|
||||
|
||||
if Daemon.rigctldprocess.returncode in [None, "None"] or not Daemon.rigctldstarted:
|
||||
Daemon.rigctldstarted = True
|
||||
# outs, errs = Daemon.rigctldprocess.communicate(timeout=10)
|
||||
# print(f"outs: {outs}")
|
||||
# print(f"errs: {errs}")
|
||||
|
||||
else:
|
||||
self.log.warning("[DMN] [RIGCTLD] [Watchdog] returncode detected",process=Daemon.rigctldprocess)
|
||||
Daemon.rigctldstarted = False
|
||||
# triggering another kill
|
||||
Daemon.rigctldprocess.kill()
|
||||
# erase process object
|
||||
Daemon.rigctldprocess = None
|
||||
else:
|
||||
Daemon.rigctldstarted = False
|
||||
|
||||
|
||||
def update_audio_devices(self):
|
||||
"""
|
||||
Update audio devices and set to static
|
||||
|
@ -192,13 +224,7 @@ class DAEMON:
|
|||
radiocontrol = data[1]
|
||||
|
||||
# check how we want to control the radio
|
||||
if radiocontrol == "direct":
|
||||
print("direct hamlib support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif radiocontrol == "rigctl":
|
||||
print("rigctl support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif radiocontrol == "rigctld":
|
||||
if radiocontrol == "rigctld":
|
||||
import rigctld as rig
|
||||
rigctld_ip = data[2]
|
||||
rigctld_port = data[3]
|
||||
|
@ -368,23 +394,25 @@ class DAEMON:
|
|||
options.append(o)
|
||||
|
||||
# append debugging paramter
|
||||
options.append(("-vvv"))
|
||||
# disabled as this could be set via gui
|
||||
#options.append(("-vvv"))
|
||||
command += options
|
||||
|
||||
self.log.info("[DMN] starting rigctld: ", param=command)
|
||||
|
||||
if not isWin:
|
||||
proc = subprocess.Popen(command, stdout=subprocess.PIPE)
|
||||
# NOTE --> It seems Popen is non blocking, while run is blocking
|
||||
#proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc = subprocess.Popen(command)
|
||||
#proc = subprocess.run(command, shell=False, check=True, text=True, capture_output=True)
|
||||
else:
|
||||
#On windows, open rigctld in new window for easier troubleshooting
|
||||
proc = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_CONSOLE,close_fds=True)
|
||||
|
||||
atexit.register(proc.kill)
|
||||
|
||||
Daemon.rigctldstarted = True
|
||||
Daemon.rigctldprocess = proc
|
||||
|
||||
|
||||
atexit.register(proc.kill)
|
||||
|
||||
except Exception as err:
|
||||
self.log.warning("[DMN] err starting rigctld: ", e=err)
|
||||
|
@ -467,7 +495,7 @@ class DAEMON:
|
|||
print(data[24])
|
||||
if data[24] == "True":
|
||||
options.append("--mesh")
|
||||
print(options)
|
||||
|
||||
# safe data to config file
|
||||
config.write_entire_config(data)
|
||||
|
||||
|
|
|
@ -81,22 +81,33 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
threading.Event().wait(0.5)
|
||||
|
||||
while not SOCKET_QUEUE.empty():
|
||||
data = SOCKET_QUEUE.get()
|
||||
sock_data = bytes(data, "utf-8")
|
||||
sock_data += b"\n" # append line limiter
|
||||
|
||||
# send data to all clients
|
||||
try:
|
||||
|
||||
data = SOCKET_QUEUE.get()
|
||||
sock_data = bytes(data, "utf-8")
|
||||
sock_data += b"\n" # append line limiter
|
||||
|
||||
# send data to all connected clients
|
||||
for client in CONNECTED_CLIENTS:
|
||||
try:
|
||||
client.send(sock_data)
|
||||
except Exception as err:
|
||||
self.log.info("[SCK] Connection lost", e=err)
|
||||
# TODO Check if we really should set connection alive to false.
|
||||
# This might disconnect all other clients as well...
|
||||
self.connection_alive = False
|
||||
|
||||
try:
|
||||
self.log.warning("[SCK] removing client from sock", client=client, set=CONNECTED_CLIENTS)
|
||||
CONNECTED_CLIENTS.remove(client)
|
||||
except Exception as sockerr:
|
||||
self.log.warning("[SCK] Err remove client from CONNECTED_CLIENTS", e=sockerr, client=client, set=CONNECTED_CLIENTS)
|
||||
self.log.info("[SCK] resetting sock")
|
||||
|
||||
# TODO Check if we really should set connection alive to false.
|
||||
# This might disconnect all other clients as well...
|
||||
self.connection_alive = False
|
||||
|
||||
except Exception as err:
|
||||
self.log.debug("[SCK] catch harmless RuntimeError: Set changed size during iteration", e=err)
|
||||
self.log.debug("[SCK] err while sending data to sock", e=err)
|
||||
|
||||
# we want to transmit scatter data only once to reduce network traffic
|
||||
ModemParam.scatter = []
|
||||
|
@ -237,12 +248,15 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
ThreadedTCPRequestHandler.modem_set_record_audio(None, received_json)
|
||||
else:
|
||||
self.modem_set_record_audio(received_json)
|
||||
|
||||
# SET TX AUDIO LEVEL
|
||||
if received_json["type"] == "set" and received_json["command"] == "tx_audio_level":
|
||||
if TESTMODE:
|
||||
ThreadedTCPRequestHandler.modem_set_tx_audio_level(None, received_json)
|
||||
else:
|
||||
self.modem_set_tx_audio_level(received_json)
|
||||
|
||||
|
||||
# TRANSMIT TEST FRAME
|
||||
if received_json["type"] == "set" and received_json["command"] == "send_test_frame":
|
||||
if TESTMODE:
|
||||
|
@ -991,7 +1005,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
if (
|
||||
received_json["type"] == "set"
|
||||
and received_json["command"] == "start_modem"
|
||||
and not Daemon.modemstarted
|
||||
# and not Daemon.modemstarted
|
||||
):
|
||||
self.daemon_start_modem(received_json)
|
||||
|
||||
|
@ -1130,6 +1144,9 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
try:
|
||||
log.warning("[SCK] Stopping Modem")
|
||||
Daemon.modemstarted = False
|
||||
# we need to run this twice, otherwise process won't be stopped
|
||||
Daemon.modemprocess.kill()
|
||||
threading.Event().wait(0.3)
|
||||
Daemon.modemprocess.kill()
|
||||
# unregister process from atexit to avoid process zombies
|
||||
atexit.unregister(Daemon.modemprocess.kill)
|
||||
|
@ -1409,10 +1426,11 @@ def send_modem_state():
|
|||
|
||||
try:
|
||||
json_out = json.dumps(output)
|
||||
except Exception as e:
|
||||
log.warning("[SCK] error while json conversion for modem state", e=e)
|
||||
return json_out
|
||||
|
||||
except Exception as e:
|
||||
log.warning("[SCK] error while json conversion for modem state", e=e, data=output)
|
||||
|
||||
return json_out
|
||||
|
||||
|
||||
def command_response(command, status):
|
||||
|
|
Loading…
Reference in a new issue