mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge branch 'develop' into dev-interface-tests
This commit is contained in:
commit
6b4bdb4d7d
41 changed files with 412 additions and 2140 deletions
2
.github/workflows/build_server.yml
vendored
2
.github/workflows/build_server.yml
vendored
|
@ -48,7 +48,7 @@ jobs:
|
|||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
export PYTHONPATH=/usr/local/lib/:$PYTHONPATH
|
||||
export PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.11/lib/:$PYTHONPATH
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "FreeDATA",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"version": "0.14.1-alpha",
|
||||
"version": "0.14.3-alpha",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -40,12 +40,10 @@
|
|||
"blob-util": "2.0.2",
|
||||
"bootstrap": "5.3.2",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"bootswatch": "5.3.2",
|
||||
"browser-image-compression": "2.0.2",
|
||||
"chart.js": "4.4.1",
|
||||
"chartjs-plugin-annotation": "3.0.1",
|
||||
"electron-log": "5.1.1",
|
||||
"electron-updater": "6.1.7",
|
||||
"emoji-picker-element": "1.21.0",
|
||||
"emoji-picker-element-data": "1.6.0",
|
||||
"file-saver": "2.0.5",
|
||||
|
@ -57,7 +55,7 @@
|
|||
"qth-locator": "2.1.0",
|
||||
"socket.io": "4.7.4",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "3.4.15",
|
||||
"vue": "3.4.21",
|
||||
"vue-chartjs": "5.3.0",
|
||||
"vuemoji-picker": "0.2.0"
|
||||
},
|
||||
|
@ -65,7 +63,7 @@
|
|||
"@types/nconf": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"electron": "28.2.2",
|
||||
"electron": "28.2.6",
|
||||
"electron-builder": "24.9.1",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
|
@ -74,13 +72,13 @@
|
|||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.20.1",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.1.3",
|
||||
"vite-plugin-electron": "0.28.2",
|
||||
"vite-plugin-electron-renderer": "0.14.5",
|
||||
"vitest": "1.2.2",
|
||||
"vue": "3.4.15",
|
||||
"vue": "3.4.21",
|
||||
"vue-tsc": "1.8.27"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 125 KiB |
Binary file not shown.
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
Before Width: | Height: | Size: 342 KiB |
|
@ -1,3 +0,0 @@
|
|||
<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>
|
Before Width: | Height: | Size: 225 B |
Binary file not shown.
Before Width: | Height: | Size: 136 KiB |
|
@ -25,7 +25,7 @@ import {
|
|||
} from "chart.js";
|
||||
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { ref, computed } from "vue";
|
||||
import { watch, nextTick, ref, computed } from "vue";
|
||||
import annotationPlugin from "chartjs-plugin-annotation";
|
||||
|
||||
ChartJS.register(
|
||||
|
@ -101,6 +101,20 @@ const beaconHistogramData = computed(() => ({
|
|||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const messagesContainer = ref(null);
|
||||
watch(
|
||||
() => chat.scrollTrigger,
|
||||
(newVal, oldVal) => {
|
||||
//console.log("Trigger changed from", oldVal, "to", newVal); // Debugging line
|
||||
nextTick(() => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop =
|
||||
messagesContainer.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -143,7 +157,7 @@ const beaconHistogramData = computed(() => ({
|
|||
</nav>
|
||||
|
||||
<!-- Chat Messages Area -->
|
||||
<div class="flex-grow-1 overflow-auto">
|
||||
<div class="flex-grow-1 overflow-auto" ref="messagesContainer">
|
||||
<chat_messages />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -13,11 +13,7 @@ const chat = useChatStore(pinia);
|
|||
function chatSelected(callsign) {
|
||||
chat.selectedCallsign = callsign.toUpperCase();
|
||||
// scroll message container to bottom
|
||||
var messageBody = document.getElementById("message-container");
|
||||
if (messageBody != null) {
|
||||
// needs sensible defaults
|
||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
||||
}
|
||||
chat.triggerScrollToBottom();
|
||||
|
||||
processBeaconData(callsign);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
Legend
|
||||
} from 'chart.js'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
|
||||
|
||||
import { VuemojiPicker, EmojiClickEventDetail } from 'vuemoji-picker'
|
||||
|
@ -90,6 +90,8 @@ function transmitNewMessage() {
|
|||
chat.selectedCallsign = Object.keys(chat.callsign_list)[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
chat.inputText = chat.inputText.trim();
|
||||
|
||||
// Proceed only if there is text or files selected
|
||||
|
@ -101,6 +103,7 @@ function transmitNewMessage() {
|
|||
type: file.type,
|
||||
data: file.content
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (chat.selectedCallsign.startsWith("BC-")) {
|
||||
|
@ -120,6 +123,7 @@ function transmitNewMessage() {
|
|||
chat.inputText = '';
|
||||
chatModuleMessage.value = "";
|
||||
resetFile()
|
||||
|
||||
}
|
||||
|
||||
function resetFile(event){
|
||||
|
|
|
@ -832,7 +832,7 @@ function quickfill() {
|
|||
<h6>15m</h6>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(14093000)">
|
||||
<a href="#" class="list-group-item list-group-item-action" @click="updateFrequencyAndApply(18106000)">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">18.106 MHz</h5>
|
||||
<small>EU / US</small>
|
||||
|
|
|
@ -21,6 +21,14 @@ function startStopBeacon() {
|
|||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
|
|
|
@ -21,6 +21,14 @@ function startStopBeacon() {
|
|||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
|
|
|
@ -34,6 +34,10 @@ function getMaidenheadDistance(dxGrid) {
|
|||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
|
@ -61,7 +65,7 @@ function getMaidenheadDistance(dxGrid) {
|
|||
</thead>
|
||||
<tbody id="gridHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin">
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
{{ getDateTime(item.timestamp) }}
|
||||
</td>
|
||||
|
|
|
@ -34,6 +34,10 @@ function getMaidenheadDistance(dxGrid) {
|
|||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
|
@ -54,7 +58,7 @@ function getMaidenheadDistance(dxGrid) {
|
|||
</thead>
|
||||
<tbody id="miniHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin">
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
<span class="fs-6">{{ getDateTime(item.timestamp) }}</span>
|
||||
</td>
|
||||
|
|
|
@ -12,6 +12,15 @@ function transmitPing() {
|
|||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group" style="width: calc(100% - 24px)">
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import infoScreen_updater from "./infoScreen_updater.vue";
|
||||
|
||||
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: "Codec 2 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",
|
||||
},
|
||||
{
|
||||
titleName: "Pedro",
|
||||
titleCall: "F4JAW",
|
||||
role: "Core Developer",
|
||||
imgSrc: "person-fill.svg",
|
||||
},
|
||||
]);
|
||||
|
||||
// Shuffle cards
|
||||
function shuffleCards() {
|
||||
cards.value = cards.value.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
onMounted(shuffleCards);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!--<infoScreen_updater />-->
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<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>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<h6>We would like to especially thank the following</h6>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex flex-nowrap overflow-y-auto w-100"
|
||||
style="height: calc(100vh - 170px); overflow-x: hidden"
|
||||
>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 row-cols-lg-6">
|
||||
<div class="d-inline-block" v-for="card in cards" :key="card.titleName">
|
||||
<div class="col">
|
||||
<div class="card border-dark m-1" style="max-width: 10rem">
|
||||
<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>
|
|
@ -5,18 +5,10 @@ setActivePinia(pinia);
|
|||
|
||||
import main_modals from "./main_modals.vue";
|
||||
import main_top_navbar from "./main_top_navbar.vue";
|
||||
import main_rig_control from "./main_rig_control.vue";
|
||||
import settings_view from "./settings.vue";
|
||||
import main_active_rig_control from "./main_active_rig_control.vue";
|
||||
import main_footer_navbar from "./main_footer_navbar.vue";
|
||||
|
||||
import main_active_stats from "./main_active_stats.vue";
|
||||
import main_active_broadcasts from "./main_active_broadcasts.vue";
|
||||
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 main_modem_healthcheck from "./main_modem_healthcheck.vue";
|
||||
import Dynamic_components from "./dynamic_components.vue";
|
||||
|
||||
|
@ -82,17 +74,6 @@ import { loadAllData } from "../js/eventHandler";
|
|||
><i class="bi bi-rocket h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-info"
|
||||
title="About"
|
||||
><i class="bi bi-info h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-logger-list"
|
||||
|
@ -302,14 +283,7 @@ import { loadAllData } from "../js/eventHandler";
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-info"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-info-list"
|
||||
>
|
||||
<infoScreen />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="list-grid"
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong>Audio</strong>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<button
|
||||
type="button"
|
||||
id="audioModalButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioModal"
|
||||
class="btn btn-sm btn-outline-secondary me-1"
|
||||
>
|
||||
Tune
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalAudioLevel"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioLevelHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.s_meter_strength_percent + '%' }"
|
||||
aria-valuenow="{{state.s_meter_strength_percent}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="noise_level_value"
|
||||
>
|
||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.dbfs_level_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="dbfs_level_value"
|
||||
>
|
||||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 8px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,110 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings} from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendModemCQ, sendModemPing, setModemBeacon } from "../js/api.js";
|
||||
|
||||
function transmitPing() {
|
||||
sendModemPing((<HTMLInputElement>document.getElementById("dxCall")).value);
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
}
|
||||
else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Broadcasts</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalBroadcasts"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#broadcastsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCall"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="sendModemCQ()"
|
||||
>
|
||||
Call CQ
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="startBeacon"
|
||||
class="btn btn-sm ms-1"
|
||||
@click="startStopBeacon()"
|
||||
v-bind:class="{
|
||||
'btn-success': state.beacon_state === true,
|
||||
'btn-outline-secondary': state.beacon_state === false,
|
||||
}"
|
||||
title="Toggle beacon mode. The interval can be set in settings. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is opened, the beacon pauses."
|
||||
>
|
||||
<i class="bi bi-soundwave"></i> Toggle beacon
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,122 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
if (typeof dxGrid != "undefined") {
|
||||
try {
|
||||
return parseInt(distance(settings.remote.STATION.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1 h-100">
|
||||
<!--325px-->
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Heard stations</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalHeardStations"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#heardStationsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0" style="overflow-y: overlay">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">
|
||||
<i id="hslSort" class="bi bi-sort-up"></i>Time
|
||||
</th>
|
||||
<th scope="col" id="thFreq">Freq</th>
|
||||
<th scope="col" id="thDxcall">DXCall</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</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.origin">
|
||||
<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.origin }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.gridsquare }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary"
|
||||
>{{ getMaidenheadDistance(item.gridsquare) }} km</span
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.activity_type }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.snr }}</span>
|
||||
</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,261 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../js/api";
|
||||
|
||||
function updateFrequencyAndApply(frequency) {
|
||||
state.new_frequency = frequency;
|
||||
set_radio_parameter_frequency();
|
||||
}
|
||||
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5 me-2">Radio control</strong>
|
||||
<span
|
||||
class="badge"
|
||||
v-bind:class="{
|
||||
'text-bg-success': state.hamlib_status === 'connected',
|
||||
'text-bg-danger disabled':
|
||||
state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>{{ state.hamlib_status }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="input-group input-group-sm bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text">{{ state.frequency }} Hz</span>
|
||||
|
||||
<!-- Dropdown Button -->
|
||||
<button
|
||||
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"
|
||||
></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="updateFrequencyAndApply(state.new_frequency)"
|
||||
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 input-group-sm">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_radio_parameter_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="USB">USB</option>
|
||||
<option value="USB-D">USB-D</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_radio_parameter_rflevel()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="0">-</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
<option value="60">60</option>
|
||||
<option value="70">70</option>
|
||||
<option value="80">80</option>
|
||||
<option value="90">90</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,402 +0,0 @@
|
|||
<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 { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
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 { Line, Scatter } from "vue-chartjs";
|
||||
import { computed } from "vue";
|
||||
|
||||
function selectStatsControl(obj) {
|
||||
switch (obj.delegateTarget.id) {
|
||||
case "list-waterfall-list":
|
||||
settings.local.spectrum = "waterfall";
|
||||
break;
|
||||
case "list-scatter-list":
|
||||
settings.local.spectrum = "scatter";
|
||||
break;
|
||||
case "list-chart-list":
|
||||
settings.local.spectrum = "chart";
|
||||
break;
|
||||
default:
|
||||
settings.local.spectrum = "waterfall";
|
||||
}
|
||||
//saveSettingsToFile();
|
||||
}
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const skipped = (speedCtx, value) =>
|
||||
speedCtx.p0.skip || speedCtx.p1.skip ? value : undefined;
|
||||
const down = (speedCtx, value) =>
|
||||
speedCtx.p0.parsed.y > speedCtx.p1.parsed.y ? value : undefined;
|
||||
|
||||
var transmissionSpeedChartOptions = {
|
||||
//type: "line",
|
||||
responsive: true,
|
||||
animations: true,
|
||||
maintainAspectRatio: false,
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
scales: {
|
||||
SNR: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(255, 99, 132)" },
|
||||
position: "right",
|
||||
},
|
||||
SPEED: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(120, 100, 120)" },
|
||||
position: "left",
|
||||
grid: {
|
||||
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||
},
|
||||
},
|
||||
x: { ticks: { beginAtZero: true } },
|
||||
},
|
||||
};
|
||||
|
||||
const transmissionSpeedChartData = computed(() => ({
|
||||
labels: state.arq_speed_list_timestamp,
|
||||
datasets: [
|
||||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
borderColor: (speedCtx) =>
|
||||
skipped(speedCtx, "rgb(0,0,0,0.4)") ||
|
||||
down(speedCtx, "rgb(192,75,75)"),
|
||||
borderDash: (speedCtx) => skipped(speedCtx, [3, 3]),
|
||||
},
|
||||
spanGaps: true,
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
order: 1,
|
||||
yAxisID: "SNR",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
yAxisID: "SPEED",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
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: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// dummy data
|
||||
//state.scatter = [{"x":"166","y":"46"},{"x":"-193","y":"-139"},{"x":"-165","y":"-291"},{"x":"311","y":"-367"},{"x":"389","y":"199"},{"x":"78","y":"372"},{"x":"242","y":"-431"},{"x":"-271","y":"-248"},{"x":"28","y":"-130"},{"x":"-20","y":"187"},{"x":"74","y":"362"},{"x":"-316","y":"-229"},{"x":"-180","y":"261"},{"x":"321","y":"360"},{"x":"438","y":"-288"},{"x":"378","y":"-94"},{"x":"462","y":"-163"},{"x":"-265","y":"248"},{"x":"210","y":"314"},{"x":"230","y":"-320"},{"x":"261","y":"-244"},{"x":"-283","y":"-373"}]
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { initWaterfall, setColormap } from "../js/waterfallHandler.js";
|
||||
var localSpectrum;
|
||||
export default {
|
||||
mounted() {
|
||||
// This code will be executed after the component is mounted to the DOM
|
||||
// You can access DOM elements or perform other initialization here
|
||||
//const myElement = this.$refs.waterfall; // Access the DOM element with ref
|
||||
|
||||
// init waterfall
|
||||
localSpectrum = initWaterfall("waterfall-main");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1" style="height: calc(var(--variable-height) - 20px)">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-11 p-0">
|
||||
<div class="btn-group h-100" role="group">
|
||||
<div
|
||||
class="list-group bg-body-tertiary list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-waterfall"
|
||||
v-bind:class="{
|
||||
active: settings.local.spectrum === 'waterfall',
|
||||
}"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-scatter"
|
||||
v-bind:class="{
|
||||
active: settings.local.spectrum === 'scatter',
|
||||
}"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-chart"
|
||||
v-bind:class="{ active: settings.local.spectrum === 'chart' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-graph-up-arrow"></i></strong
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Busy indicators">
|
||||
<button
|
||||
class="btn btn-sm ms-1 p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[0] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[0] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S1
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[1] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[1] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[2] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[2] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S3
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[3] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[3] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S4
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[4] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[4] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S5
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Recieving data: illuminates <strong class='text-success'>green</strong> if receiving codec2 data"
|
||||
v-bind:class="{
|
||||
'btn-success': state.is_codec2_traffic === true,
|
||||
'btn-outline-secondary': state.is_codec2_traffic === false,
|
||||
}"
|
||||
>
|
||||
data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalWaterfall"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#waterfallHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<div class="tab-content" id="nav-stats-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{
|
||||
'show active': settings.local.spectrum === 'waterfall',
|
||||
}"
|
||||
id="list-waterfall"
|
||||
role="stats_tabpanel"
|
||||
aria-labelledby="list-waterfall-list"
|
||||
>
|
||||
<canvas
|
||||
ref="waterfall-main"
|
||||
id="waterfall-main"
|
||||
style="
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
aspect-ratio: unset;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
"
|
||||
class="force-gpu'"
|
||||
></canvas>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{
|
||||
'show active': settings.local.spectrum === 'scatter',
|
||||
}"
|
||||
id="list-scatter"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-scatter-list"
|
||||
>
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.local.spectrum === 'chart' }"
|
||||
id="list-chart"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chart-list"
|
||||
>
|
||||
<Line
|
||||
:data="transmissionSpeedChartData"
|
||||
:options="transmissionSpeedChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--278px-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,62 +0,0 @@
|
|||
<script setup>
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audio = useAudioStore(pinia);
|
||||
|
||||
import { setConfig } from "../js/api";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Audio devices</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-mic-fill" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getInputDevices()"
|
||||
@change="setConfig"
|
||||
></select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getOutputDevices()"
|
||||
@change="setConfig"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,114 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">My station</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your callsign and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person-bounding-box" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="width: 5rem; text-transform: uppercase"
|
||||
placeholder="callsign"
|
||||
pattern="[A-Z]*"
|
||||
id="myCall"
|
||||
maxlength="8"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="settings.remote.STATION.mycall"
|
||||
/>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="myCallSSID"
|
||||
v-model="settings.remote.STATION.myssid"
|
||||
>
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your gridsquare and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-house-fill" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mr-1"
|
||||
style="max-width: 6rem"
|
||||
placeholder="locator"
|
||||
id="myGrid"
|
||||
maxlength="6"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="settings.remote.STATION.mygrid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,235 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function selectRadioControl() {
|
||||
// @ts-expect-error
|
||||
switch (event.target.id) {
|
||||
case "list-rig-control-none-list":
|
||||
settings.remote.RADIO.control = "disabled";
|
||||
break;
|
||||
case "list-rig-control-rigctld-list":
|
||||
settings.remote.RADIO.control = "rigctld";
|
||||
break;
|
||||
case "list-rig-control-rigctld-list":
|
||||
settings.remote.RADIO.control = "rigctld_bundle";
|
||||
break;
|
||||
|
||||
case "list-rig-control-tci-list":
|
||||
settings.remote.RADIO.control = "tci";
|
||||
break;
|
||||
default:
|
||||
console.log("default=!==");
|
||||
settings.remote.RADIO.control = "disabled";
|
||||
}
|
||||
//saveSettingsToFile();
|
||||
}
|
||||
|
||||
function testHamlib() {
|
||||
console.log("not yet implemented");
|
||||
alert("not yet implemented");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-projector" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<strong class="fs-5">Rig control</strong>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div
|
||||
class="list-group bg-body-tertiary list-group-horizontal w-75"
|
||||
id="rig-control-list-tab"
|
||||
role="rig-control-tablist"
|
||||
>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-none"
|
||||
v-bind:class="{
|
||||
active: settings.remote.RADIO.control === 'disabled',
|
||||
}"
|
||||
@click="selectRadioControl()"
|
||||
>None</a
|
||||
>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-rigctld"
|
||||
v-bind:class="{
|
||||
active: settings.remote.RADIO.control === 'rigctld',
|
||||
}"
|
||||
@click="selectRadioControl()"
|
||||
>Rigctld</a
|
||||
>
|
||||
<a
|
||||
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"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-tci"
|
||||
v-bind:class="{
|
||||
active: settings.remote.RADIO.control === 'tci',
|
||||
}"
|
||||
@click="selectRadioControl()"
|
||||
>TCI</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalRigControl"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rigcontrolHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="tab-content" id="rig-control-nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{
|
||||
'show active': settings.remote.RADIO.control === 'disabled',
|
||||
}"
|
||||
id="list-rig-control-none"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-none-list"
|
||||
>
|
||||
<p class="small">
|
||||
Modem will not utilize rig control and features will be limited.
|
||||
While functional; it is recommended to configure hamlib. <br />
|
||||
Use this setting also for <strong> VOX </strong>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-rigctld"
|
||||
v-bind:class="{
|
||||
'show active': settings.remote.RADIO.control === 'rigctld',
|
||||
}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-rigctld-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Rigctld service</span>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Status"
|
||||
id="hamlib_rigctld_status"
|
||||
aria-label="State"
|
||||
aria-describedby="basic-addon1"
|
||||
v-model="state.rigctld_started"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="testHamlib"
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
@click="testHamlib"
|
||||
title="Test your hamlib settings and toggle PTT once. Button will become <strong class='text-success'>green</strong> on success and <strong class='text-danger'>red</strong> if fails."
|
||||
>
|
||||
PTT Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-tci"
|
||||
v-bind:class="{
|
||||
'show active': settings.remote.RADIO.control === 'tci',
|
||||
}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-tci-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TCI</span>
|
||||
<span class="input-group-text">Address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci IP"
|
||||
id="tci_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.remote.TCI.tci_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci port"
|
||||
id="tci_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.remote.TCI.tci_port"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RADIO CONTROL DISABLED -->
|
||||
<div id="radio-control-disabled"></div>
|
||||
|
||||
<!-- RADIO CONTROL RIGCTLD -->
|
||||
<div id="radio-control-rigctld"></div>
|
||||
<!-- RADIO CONTROL TCI-->
|
||||
<div id="radio-control-tci"></div>
|
||||
<!-- RADIO CONTROL HELP -->
|
||||
<div id="radio-control-help">
|
||||
<!--
|
||||
<strong>VOX:</strong> Use rig control mode 'none'
|
||||
<br />
|
||||
<strong>HAMLIB locally:</strong> configure in settings, then
|
||||
start/stop service.
|
||||
<br />
|
||||
<strong>HAMLIB remotely:</strong> Enter IP/Port, connection
|
||||
happens automatically.
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="card-footer text-muted small" id="hamlib_info_field">
|
||||
Define Modem rig control mode (none/hamlib)
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -164,56 +164,6 @@ const audioStore = useAudioStore();
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-25">Tuning range</label>
|
||||
<label class="input-group-text">fmin</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmin"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.tuning_range_fmin"
|
||||
>
|
||||
<option value="-50">-50</option>
|
||||
<option value="-100">-100</option>
|
||||
<option value="-150">-150</option>
|
||||
<option value="-200">-200</option>
|
||||
<option value="-250">-250</option>
|
||||
</select>
|
||||
<label class="input-group-text">fmax</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmax"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.tuning_range_fmax"
|
||||
>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="150">150</option>
|
||||
<option value="200">200</option>
|
||||
<option value="250">250</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Beacon interval</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="beaconInterval"
|
||||
style="width: 6rem"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.beacon_interval"
|
||||
>
|
||||
<option value="60">60 secs</option>
|
||||
<option value="90">90 secs</option>
|
||||
<option value="120">2 mins</option>
|
||||
<option selected value="300">5 mins</option>
|
||||
<option value="600">10 mins</option>
|
||||
<option value="900">15 mins</option>
|
||||
<option value="1800">30 mins</option>
|
||||
<option value="3600">60 mins</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable 250Hz bandwidth mode</label>
|
||||
<label class="input-group-text w-50">
|
||||
|
@ -244,27 +194,4 @@ const audioStore = useAudioStore();
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">RX buffer size</label>
|
||||
<label class="input-group-text w-50">
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="rx_buffer_size"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.rx_buffer_size"
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="4">4</option>
|
||||
<option value="8">8</option>
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
<option value="512">512</option>
|
||||
<option value="1024">1024</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -83,6 +83,7 @@ function createSortedMessagesList(data: {
|
|||
|
||||
export function newMessage(dxcall, body, attachments) {
|
||||
sendFreedataMessage(dxcall, body, attachments);
|
||||
chatStore.triggerScrollToBottom();
|
||||
}
|
||||
|
||||
/* ------ TEMPORARY DUMMY FUNCTIONS --- */
|
||||
|
|
|
@ -7,6 +7,14 @@ export const useChatStore = defineStore("chatStore", () => {
|
|||
var newChatCallsign = ref();
|
||||
var newChatMessage = ref();
|
||||
|
||||
/* ------------------------------------------------ */
|
||||
// Scroll to bottom functions
|
||||
const scrollTrigger = ref(0);
|
||||
|
||||
function triggerScrollToBottom() {
|
||||
scrollTrigger.value++;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ */
|
||||
|
||||
var chat_filter = ref([
|
||||
|
@ -92,5 +100,7 @@ export const useChatStore = defineStore("chatStore", () => {
|
|||
arq_speed_list_bpm,
|
||||
arq_speed_list_snr,
|
||||
arq_speed_list_timestamp,
|
||||
scrollTrigger,
|
||||
triggerScrollToBottom,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -54,14 +54,9 @@ const defaultConfig = {
|
|||
enable_protocol: false,
|
||||
},
|
||||
MODEM: {
|
||||
enable_fsk: false,
|
||||
enable_low_bandwidth_mode: false,
|
||||
respond_to_cq: false,
|
||||
rx_buffer_size: 0,
|
||||
tuning_range_fmax: 0,
|
||||
tuning_range_fmin: 0,
|
||||
tx_delay: 0,
|
||||
beacon_interval: 0,
|
||||
enable_hamc: false,
|
||||
enable_morse_identifier: false,
|
||||
},
|
||||
|
|
|
@ -152,6 +152,12 @@ class ARQSession():
|
|||
self.snr_histogram.append(self.snr)
|
||||
self.bpm_histogram.append(stats['bytes_per_minute'])
|
||||
self.time_histogram.append(datetime.datetime.now().isoformat())
|
||||
|
||||
# Limit the size of each histogram to the last 20 entries
|
||||
self.snr_histogram = self.snr_histogram[-20:]
|
||||
self.bpm_histogram = self.bpm_histogram[-20:]
|
||||
self.time_histogram = self.time_histogram[-20:]
|
||||
|
||||
return stats
|
||||
|
||||
def get_appropriate_speed_level(self, snr):
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
"""
|
||||
Gather information about audio devices.
|
||||
"""
|
||||
import atexit
|
||||
import multiprocessing
|
||||
import crcengine
|
||||
import sounddevice as sd
|
||||
import structlog
|
||||
import numpy as np
|
||||
import queue
|
||||
import threading
|
||||
|
||||
atexit.register(sd._terminate)
|
||||
|
||||
log = structlog.get_logger("audio")
|
||||
|
||||
|
|
|
@ -23,6 +23,11 @@ class SendMessageCommand(TxCommand):
|
|||
self.log("Modem busy, waiting until ready...")
|
||||
return
|
||||
|
||||
if not modem:
|
||||
self.log("Modem not running...", isWarning=True)
|
||||
return
|
||||
|
||||
|
||||
first_queued_message = DatabaseManagerMessages(self.event_manager).get_first_queued_message()
|
||||
if not first_queued_message:
|
||||
self.log("No queued message in database.")
|
||||
|
|
|
@ -15,7 +15,6 @@ input_device = 5a1c
|
|||
output_device = bd6c
|
||||
rx_audio_level = 0
|
||||
tx_audio_level = 0
|
||||
enable_auto_tune = False
|
||||
|
||||
[RIGCTLD]
|
||||
ip = 127.0.0.1
|
||||
|
@ -46,15 +45,10 @@ enable_protocol = False
|
|||
|
||||
[MODEM]
|
||||
enable_hmac = False
|
||||
tuning_range_fmax = 50
|
||||
tuning_range_fmin = -50
|
||||
enable_fsk = False
|
||||
enable_low_bandwidth_mode = False
|
||||
enable_morse_identifier = False
|
||||
respond_to_cq = True
|
||||
rx_buffer_size = 64
|
||||
tx_delay = 200
|
||||
beacon_interval = 300
|
||||
|
||||
[MESSAGES]
|
||||
enable_auto_repeat = False
|
||||
|
|
|
@ -26,7 +26,6 @@ class CONFIG:
|
|||
'output_device': str,
|
||||
'rx_audio_level': int,
|
||||
'tx_audio_level': int,
|
||||
'enable_auto_tune': bool,
|
||||
},
|
||||
'RADIO': {
|
||||
'control': str,
|
||||
|
@ -56,16 +55,11 @@ class CONFIG:
|
|||
'enable_protocol': bool,
|
||||
},
|
||||
'MODEM': {
|
||||
'tuning_range_fmax': int,
|
||||
'tuning_range_fmin': int,
|
||||
'enable_fsk': bool,
|
||||
'enable_hmac': bool,
|
||||
'enable_morse_identifier': bool,
|
||||
'enable_low_bandwidth_mode': bool,
|
||||
'respond_to_cq': bool,
|
||||
'rx_buffer_size': int,
|
||||
'tx_delay': int,
|
||||
'beacon_interval': int,
|
||||
'tx_delay': int
|
||||
},
|
||||
'MESSAGES': {
|
||||
'enable_auto_repeat': bool,
|
||||
|
|
|
@ -4,8 +4,6 @@ import ctypes
|
|||
import structlog
|
||||
import threading
|
||||
import audio
|
||||
import os
|
||||
from modem_frametypes import FRAME_TYPE
|
||||
import itertools
|
||||
|
||||
TESTMODE = False
|
||||
|
@ -27,20 +25,16 @@ class Demodulator():
|
|||
'decoding_thread': None
|
||||
}
|
||||
|
||||
def __init__(self, config, audio_rx_q, modem_rx_q, data_q_rx, states, event_manager, fft_queue):
|
||||
def __init__(self, config, audio_rx_q, data_q_rx, states, event_manager, service_queue, fft_queue):
|
||||
self.log = structlog.get_logger("Demodulator")
|
||||
|
||||
self.tuning_range_fmin = config['MODEM']['tuning_range_fmin']
|
||||
self.tuning_range_fmax = config['MODEM']['tuning_range_fmax']
|
||||
self.rx_audio_level = config['AUDIO']['rx_audio_level']
|
||||
|
||||
self.service_queue = service_queue
|
||||
self.AUDIO_FRAMES_PER_BUFFER_RX = 4800
|
||||
self.buffer_overflow_counter = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
self.is_codec2_traffic_counter = 0
|
||||
self.is_codec2_traffic_cooldown = 5
|
||||
|
||||
self.audio_received_queue = audio_rx_q
|
||||
self.modem_received_queue = modem_rx_q
|
||||
self.data_queue_received = data_q_rx
|
||||
|
||||
self.states = states
|
||||
|
@ -79,13 +73,6 @@ class Demodulator():
|
|||
codec2.api.freedv_open(mode), ctypes.c_void_p
|
||||
)
|
||||
|
||||
# set tuning range
|
||||
codec2.api.freedv_set_tuning_range(
|
||||
c2instance,
|
||||
ctypes.c_float(float(self.tuning_range_fmin)),
|
||||
ctypes.c_float(float(self.tuning_range_fmax)),
|
||||
)
|
||||
|
||||
# get bytes per frame
|
||||
bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
||||
|
@ -135,49 +122,6 @@ class Demodulator():
|
|||
)
|
||||
self.MODE_DICT[mode]['decoding_thread'].start()
|
||||
|
||||
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_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
|
||||
# selected datachannel mode is not full
|
||||
index = 0
|
||||
for mode in self.MODE_DICT:
|
||||
mode_data = self.MODE_DICT[mode]
|
||||
audiobuffer = mode_data['audio_buffer']
|
||||
decode = mode_data['decode']
|
||||
index += 1
|
||||
if audiobuffer:
|
||||
if (audiobuffer.nbuffer + length_audio_8k_level_adjusted) > audiobuffer.size:
|
||||
self.buffer_overflow_counter[index] += 1
|
||||
self.event_manager.send_buffer_overflow(self.buffer_overflow_counter)
|
||||
elif decode:
|
||||
audiobuffer.push(audio_8k_level_adjusted)
|
||||
|
||||
def worker_received(self) -> None:
|
||||
"""Worker for FIFO queue for processing received frames"""
|
||||
while True:
|
||||
data = self.modem_received_queue.get()
|
||||
self.log.debug("[MDM] worker_received: received data!")
|
||||
# data[0] = bytes_out
|
||||
# data[1] = freedv session
|
||||
# data[2] = bytes_per_frame
|
||||
# data[3] = snr
|
||||
|
||||
item = {
|
||||
'payload': data[0],
|
||||
'freedv': data[1],
|
||||
'bytes_per_frame': data[2],
|
||||
'snr': data[3],
|
||||
'frequency_offset': self.get_frequency_offset(data[1]),
|
||||
}
|
||||
|
||||
self.data_queue_received.put(item)
|
||||
self.modem_received_queue.task_done()
|
||||
|
||||
def get_frequency_offset(self, freedv: ctypes.c_void_p) -> float:
|
||||
"""
|
||||
|
@ -247,7 +191,16 @@ class Demodulator():
|
|||
snr = self.calculate_snr(freedv)
|
||||
self.get_scatter(freedv)
|
||||
|
||||
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame, snr])
|
||||
item = {
|
||||
'payload': bytes_out,
|
||||
'freedv': freedv,
|
||||
'bytes_per_frame': bytes_per_frame,
|
||||
'snr': snr,
|
||||
'frequency_offset': self.get_frequency_offset(freedv),
|
||||
}
|
||||
self.data_queue_received.put(item)
|
||||
|
||||
|
||||
state_buffer = []
|
||||
except Exception as e:
|
||||
error_message = str(e)
|
||||
|
@ -257,6 +210,7 @@ class Demodulator():
|
|||
self.log.debug(
|
||||
"[MDM] [demod_audio] demod loop ended", mode=mode_name, e=e
|
||||
)
|
||||
|
||||
def tci_rx_callback(self) -> None:
|
||||
"""
|
||||
Callback for TCI RX
|
||||
|
@ -297,6 +251,7 @@ class Demodulator():
|
|||
frames_per_burst = min(frames_per_burst, 1)
|
||||
frames_per_burst = max(frames_per_burst, 5)
|
||||
|
||||
# FIXME
|
||||
frames_per_burst = 1
|
||||
|
||||
codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst)
|
||||
|
|
|
@ -12,6 +12,8 @@ class EventManager:
|
|||
def broadcast(self, data):
|
||||
for q in self.queues:
|
||||
self.logger.debug(f"Event: ", ev=data)
|
||||
if q.qsize() > 10:
|
||||
q.queue.clear()
|
||||
q.put(data)
|
||||
|
||||
def send_ptt_change(self, on:bool = False):
|
||||
|
|
369
modem/modem.py
369
modem/modem.py
|
@ -9,10 +9,7 @@ Created on Wed Dec 23 07:04:24 2020
|
|||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
import atexit
|
||||
import ctypes
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
import codec2
|
||||
import numpy as np
|
||||
|
@ -20,9 +17,9 @@ import sounddevice as sd
|
|||
import structlog
|
||||
import tci
|
||||
import cw
|
||||
from queues import RIGCTLD_COMMAND_QUEUE
|
||||
import audio
|
||||
import demodulator
|
||||
import modulator
|
||||
|
||||
TESTMODE = False
|
||||
|
||||
|
@ -44,50 +41,52 @@ class RF:
|
|||
self.audio_input_device = config['AUDIO']['input_device']
|
||||
self.audio_output_device = config['AUDIO']['output_device']
|
||||
|
||||
self.tx_audio_level = config['AUDIO']['tx_audio_level']
|
||||
self.enable_audio_auto_tune = config['AUDIO']['enable_auto_tune']
|
||||
self.tx_delay = config['MODEM']['tx_delay']
|
||||
|
||||
|
||||
self.radiocontrol = config['RADIO']['control']
|
||||
self.rigctld_ip = config['RIGCTLD']['ip']
|
||||
self.rigctld_port = config['RIGCTLD']['port']
|
||||
|
||||
self.states.setTransmitting(False)
|
||||
|
||||
self.ptt_state = False
|
||||
self.radio_alc = 0.0
|
||||
|
||||
self.tci_ip = config['TCI']['tci_ip']
|
||||
self.tci_port = config['TCI']['tci_port']
|
||||
|
||||
self.tx_audio_level = config['AUDIO']['tx_audio_level']
|
||||
self.rx_audio_level = config['AUDIO']['rx_audio_level']
|
||||
|
||||
|
||||
self.ptt_state = False
|
||||
self.enqueuing_audio = False # set to True, while we are processing audio
|
||||
|
||||
self.AUDIO_SAMPLE_RATE = 48000
|
||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||
self.modem_sample_rate = codec2.api.FREEDV_FS_8000
|
||||
|
||||
# 8192 Let's do some tests with very small chunks for TX
|
||||
self.AUDIO_FRAMES_PER_BUFFER_TX = 1200 if self.radiocontrol in ["tci"] else 2400 * 2
|
||||
# 8 * (self.AUDIO_SAMPLE_RATE/self.MODEM_SAMPLE_RATE) == 48
|
||||
#self.AUDIO_FRAMES_PER_BUFFER_TX = 1200 if self.radiocontrol in ["tci"] else 2400 * 2
|
||||
# 8 * (self.AUDIO_SAMPLE_RATE/self.modem_sample_rate) == 48
|
||||
self.AUDIO_CHANNELS = 1
|
||||
self.MODE = 0
|
||||
self.rms_counter = 0
|
||||
|
||||
# Make sure our resampler will work
|
||||
assert (self.AUDIO_SAMPLE_RATE / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore
|
||||
self.audio_out_queue = queue.Queue()
|
||||
|
||||
# Make sure our resampler will work
|
||||
assert (self.AUDIO_SAMPLE_RATE / self.modem_sample_rate) == codec2.api.FDMDV_OS_48 # type: ignore
|
||||
|
||||
self.modem_received_queue = queue.Queue()
|
||||
self.audio_received_queue = queue.Queue()
|
||||
self.data_queue_received = queue.Queue()
|
||||
self.fft_queue = fft_queue
|
||||
|
||||
self.demodulator = demodulator.Demodulator(self.config,
|
||||
self.audio_received_queue,
|
||||
self.modem_received_queue,
|
||||
self.data_queue_received,
|
||||
self.states,
|
||||
self.event_manager,
|
||||
self.service_queue,
|
||||
self.fft_queue
|
||||
)
|
||||
|
||||
self.modulator = modulator.Modulator(self.config)
|
||||
|
||||
|
||||
|
||||
def tci_tx_callback(self, audio_48k) -> None:
|
||||
|
@ -105,11 +104,6 @@ class RF:
|
|||
if not self.init_audio():
|
||||
raise RuntimeError("Unable to init audio devices")
|
||||
self.demodulator.start(self.sd_input_stream)
|
||||
atexit.register(self.sd_input_stream.stop)
|
||||
|
||||
# Initialize codec2, rig control, and data threads
|
||||
self.init_codec2()
|
||||
self.init_data_threads()
|
||||
|
||||
return True
|
||||
|
||||
|
@ -155,17 +149,25 @@ class RF:
|
|||
self.sd_input_stream = sd.InputStream(
|
||||
channels=1,
|
||||
dtype="int16",
|
||||
callback=self.demodulator.sd_input_audio_callback,
|
||||
callback=self.sd_input_audio_callback,
|
||||
device=in_dev_index,
|
||||
samplerate=self.AUDIO_SAMPLE_RATE,
|
||||
blocksize=4800,
|
||||
)
|
||||
self.sd_input_stream.start()
|
||||
|
||||
self.sd_output_stream = sd.OutputStream(
|
||||
channels=1,
|
||||
dtype="int16",
|
||||
callback=self.sd_output_audio_callback,
|
||||
device=out_dev_index,
|
||||
samplerate=self.AUDIO_SAMPLE_RATE,
|
||||
blocksize=4800,
|
||||
)
|
||||
self.sd_output_stream.start()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
except Exception as audioerr:
|
||||
self.log.error("[MDM] init: starting pyaudio callback failed", e=audioerr)
|
||||
self.stop_modem()
|
||||
|
@ -188,191 +190,7 @@ class RF:
|
|||
|
||||
return True
|
||||
|
||||
def audio_auto_tune(self):
|
||||
# enable / disable AUDIO TUNE Feature / ALC correction
|
||||
if self.enable_audio_auto_tune:
|
||||
if self.radio_alc == 0.0:
|
||||
self.tx_audio_level = self.tx_audio_level + 20
|
||||
elif 0.0 < self.radio_alc <= 0.1:
|
||||
print("0.0 < self.radio_alc <= 0.1")
|
||||
self.tx_audio_level = self.tx_audio_level + 2
|
||||
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(self.tx_audio_level),
|
||||
alc_level=str(self.radio_alc))
|
||||
elif 0.1 < self.radio_alc < 0.2:
|
||||
print("0.1 < self.radio_alc < 0.2")
|
||||
self.tx_audio_level = self.tx_audio_level
|
||||
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(self.tx_audio_level),
|
||||
alc_level=str(self.radio_alc))
|
||||
elif 0.2 < self.radio_alc < 0.99:
|
||||
print("0.2 < self.radio_alc < 0.99")
|
||||
self.tx_audio_level = self.tx_audio_level - 20
|
||||
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(self.tx_audio_level),
|
||||
alc_level=str(self.radio_alc))
|
||||
elif 1.0 >= self.radio_alc:
|
||||
print("1.0 >= self.radio_alc")
|
||||
self.tx_audio_level = self.tx_audio_level - 40
|
||||
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(self.tx_audio_level),
|
||||
alc_level=str(self.radio_alc))
|
||||
else:
|
||||
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(self.tx_audio_level),
|
||||
alc_level=str(self.radio_alc))
|
||||
|
||||
def transmit(
|
||||
self, mode, repeats: int, repeat_delay: int, frames: bytearray
|
||||
) -> bool:
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
repeats:
|
||||
repeat_delay:
|
||||
frames:
|
||||
|
||||
"""
|
||||
if TESTMODE:
|
||||
return
|
||||
|
||||
|
||||
self.demodulator.reset_data_sync()
|
||||
# get freedv instance by mode
|
||||
mode_transition = {
|
||||
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
||||
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
||||
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
||||
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
||||
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
||||
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
||||
}
|
||||
if mode in mode_transition:
|
||||
freedv = mode_transition[mode]
|
||||
else:
|
||||
print("wrong mode.................")
|
||||
print(mode)
|
||||
return False
|
||||
|
||||
# Wait for some other thread that might be transmitting
|
||||
self.states.waitForTransmission()
|
||||
self.states.setTransmitting(True)
|
||||
#self.states.channel_busy_event.wait()
|
||||
|
||||
|
||||
start_of_transmission = time.time()
|
||||
|
||||
# Open codec2 instance
|
||||
self.MODE = mode
|
||||
|
||||
txbuffer = bytes()
|
||||
|
||||
# Add empty data to handle ptt toggle time
|
||||
if self.tx_delay > 0:
|
||||
self.transmit_add_silence(txbuffer, self.tx_delay)
|
||||
|
||||
self.log.debug(
|
||||
"[MDM] TRANSMIT", mode=self.MODE.name, delay=self.tx_delay
|
||||
)
|
||||
|
||||
if not isinstance(frames, list): frames = [frames]
|
||||
for _ in range(repeats):
|
||||
|
||||
# Create modulation for all frames in the list
|
||||
for frame in frames:
|
||||
|
||||
txbuffer = self.transmit_add_preamble(txbuffer, freedv)
|
||||
txbuffer = self.transmit_create_frame(txbuffer, freedv, frame)
|
||||
txbuffer = self.transmit_add_postamble(txbuffer, freedv)
|
||||
|
||||
# Add delay to end of frames
|
||||
self.transmit_add_silence(txbuffer, repeat_delay)
|
||||
|
||||
# Re-sample back up to 48k (resampler works on np.int16)
|
||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
||||
|
||||
self.audio_auto_tune()
|
||||
x = audio.set_audio_volume(x, self.tx_audio_level)
|
||||
|
||||
if self.radiocontrol not in ["tci"]:
|
||||
txbuffer_out = self.resampler.resample8_to_48(x)
|
||||
else:
|
||||
txbuffer_out = x
|
||||
|
||||
# transmit audio
|
||||
self.transmit_audio(txbuffer_out)
|
||||
|
||||
self.radio.set_ptt(False)
|
||||
self.event_manager.send_ptt_change(False)
|
||||
self.states.setTransmitting(False)
|
||||
|
||||
end_of_transmission = time.time()
|
||||
transmission_time = end_of_transmission - start_of_transmission
|
||||
self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
|
||||
return True
|
||||
|
||||
def transmit_add_preamble(self, buffer, freedv):
|
||||
|
||||
# Init buffer for preample
|
||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||
freedv
|
||||
)
|
||||
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
||||
|
||||
# Write preamble to txbuffer
|
||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||
buffer += bytes(mod_out_preamble)
|
||||
return buffer
|
||||
|
||||
def transmit_add_postamble(self, buffer, freedv):
|
||||
# Init buffer for postamble
|
||||
n_tx_postamble_modem_samples = (
|
||||
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||
)
|
||||
mod_out_postamble = ctypes.create_string_buffer(
|
||||
n_tx_postamble_modem_samples * 2
|
||||
)
|
||||
# Write postamble to txbuffer
|
||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||
# Append postamble to txbuffer
|
||||
buffer += bytes(mod_out_postamble)
|
||||
return buffer
|
||||
|
||||
def transmit_add_silence(self, buffer, duration):
|
||||
data_delay = int(self.MODEM_SAMPLE_RATE * (duration / 1000)) # type: ignore
|
||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
buffer += bytes(mod_out_silence)
|
||||
return buffer
|
||||
|
||||
def transmit_create_frame(self, txbuffer, freedv, frame):
|
||||
# Get number of bytes per frame for mode
|
||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||
payload_bytes_per_frame = bytes_per_frame - 2
|
||||
|
||||
# Init buffer for data
|
||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
||||
|
||||
# Create buffer for data
|
||||
# Use this if CRC16 checksum is required (DATAc1-3)
|
||||
buffer = bytearray(payload_bytes_per_frame)
|
||||
# Set buffersize to length of data which will be send
|
||||
buffer[: len(frame)] = frame # type: ignore
|
||||
|
||||
# Create crc for data frame -
|
||||
# Use the crc function shipped with codec2
|
||||
# to avoid CRC algorithm incompatibilities
|
||||
# Generate CRC16
|
||||
crc = ctypes.c_ushort(
|
||||
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
|
||||
)
|
||||
# Convert crc to 2-byte (16-bit) hex string
|
||||
crc = crc.value.to_bytes(2, byteorder="big")
|
||||
# Append CRC to data buffer
|
||||
buffer += crc
|
||||
|
||||
assert (bytes_per_frame == len(buffer))
|
||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||
# modulate DATA and save it into mod_out pointer
|
||||
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||
txbuffer += bytes(mod_out)
|
||||
return txbuffer
|
||||
|
||||
def transmit_morse(self, repeats, repeat_delay, frames):
|
||||
self.states.waitForTransmission()
|
||||
|
@ -383,36 +201,54 @@ class RF:
|
|||
)
|
||||
start_of_transmission = time.time()
|
||||
|
||||
txbuffer_out = cw.MorseCodePlayer().text_to_signal("DJ2LS-1")
|
||||
txbuffer_out = cw.MorseCodePlayer().text_to_signal(self.config['STATION'].mycall)
|
||||
|
||||
self.transmit_audio(txbuffer_out)
|
||||
self.radio.set_ptt(False)
|
||||
self.event_manager.send_ptt_change(False)
|
||||
|
||||
self.states.setTransmitting(False)
|
||||
# transmit audio
|
||||
self.enqueue_audio_out(txbuffer_out)
|
||||
|
||||
end_of_transmission = time.time()
|
||||
transmission_time = end_of_transmission - start_of_transmission
|
||||
self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
|
||||
|
||||
def init_codec2(self):
|
||||
# Open codec2 instances
|
||||
|
||||
# INIT TX MODES - here we need all modes.
|
||||
self.freedv_datac0_tx = codec2.open_instance(codec2.FREEDV_MODE.datac0.value)
|
||||
self.freedv_datac1_tx = codec2.open_instance(codec2.FREEDV_MODE.datac1.value)
|
||||
self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.value)
|
||||
self.freedv_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value)
|
||||
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
|
||||
def transmit(
|
||||
self, mode, repeats: int, repeat_delay: int, frames: bytearray
|
||||
) -> bool:
|
||||
|
||||
def init_data_threads(self):
|
||||
worker_received = threading.Thread(
|
||||
target=self.demodulator.worker_received, name="WORKER_THREAD", daemon=True
|
||||
)
|
||||
worker_received.start()
|
||||
self.demodulator.reset_data_sync()
|
||||
|
||||
# Wait for some other thread that might be transmitting
|
||||
self.states.waitForTransmission()
|
||||
self.states.setTransmitting(True)
|
||||
# self.states.channel_busy_event.wait()
|
||||
|
||||
start_of_transmission = time.time()
|
||||
txbuffer = self.modulator.create_burst(mode, repeats, repeat_delay, frames)
|
||||
|
||||
# Re-sample back up to 48k (resampler works on np.int16)
|
||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
||||
x = audio.set_audio_volume(x, self.tx_audio_level)
|
||||
|
||||
if self.radiocontrol not in ["tci"]:
|
||||
txbuffer_out = self.resampler.resample8_to_48(x)
|
||||
else:
|
||||
txbuffer_out = x
|
||||
|
||||
# transmit audio
|
||||
self.enqueue_audio_out(txbuffer_out)
|
||||
|
||||
end_of_transmission = time.time()
|
||||
transmission_time = end_of_transmission - start_of_transmission
|
||||
self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
|
||||
|
||||
|
||||
|
||||
def enqueue_audio_out(self, audio_48k) -> None:
|
||||
self.enqueuing_audio = True
|
||||
|
||||
if not self.states.isTransmitting():
|
||||
self.states.setTransmitting(True)
|
||||
|
||||
# Low level modem audio transmit
|
||||
def transmit_audio(self, audio_48k) -> None:
|
||||
self.radio.set_ptt(True)
|
||||
self.event_manager.send_ptt_change(True)
|
||||
|
||||
|
@ -421,5 +257,72 @@ class RF:
|
|||
# we need to wait manually for tci processing
|
||||
self.tci_module.wait_until_transmitted(audio_48k)
|
||||
else:
|
||||
sd.play(audio_48k, blocksize=4096, blocking=True)
|
||||
# slice audio data to needed blocklength
|
||||
block_size = 4800
|
||||
pad_length = -len(audio_48k) % block_size
|
||||
padded_data = np.pad(audio_48k, (0, pad_length), mode='constant')
|
||||
sliced_audio_data = padded_data.reshape(-1, block_size)
|
||||
# add each block to audio out queue
|
||||
for block in sliced_audio_data:
|
||||
self.audio_out_queue.put(block)
|
||||
|
||||
self.enqueuing_audio = False
|
||||
self.states.transmitting_event.wait()
|
||||
|
||||
self.radio.set_ptt(False)
|
||||
self.event_manager.send_ptt_change(False)
|
||||
|
||||
return
|
||||
|
||||
def sd_output_audio_callback(self, outdata: np.ndarray, frames: int, time, status) -> None:
|
||||
try:
|
||||
if not self.audio_out_queue.empty():
|
||||
chunk = self.audio_out_queue.get_nowait()
|
||||
audio.calculate_fft(chunk, self.fft_queue, self.states)
|
||||
outdata[:] = chunk.reshape(outdata.shape)
|
||||
|
||||
else:
|
||||
# reset transmitting state only, if we are not actively processing audio
|
||||
# for avoiding a ptt toggle state bug
|
||||
if not self.enqueuing_audio:
|
||||
self.states.setTransmitting(False)
|
||||
# Fill with zeros if the queue is empty
|
||||
outdata.fill(0)
|
||||
except Exception as e:
|
||||
self.log.warning("[AUDIO STATUS]", status=status, time=time, frames=frames, e=e)
|
||||
outdata.fill(0)
|
||||
|
||||
def sd_input_audio_callback(self, indata: np.ndarray, frames: int, time, status) -> None:
|
||||
if status:
|
||||
self.log.warning("[AUDIO STATUS]", status=status, time=time, frames=frames)
|
||||
# FIXME on windows input overflows crashing the rx audio stream. Lets restart the server then
|
||||
#if status.input_overflow:
|
||||
# self.service_queue.put("restart")
|
||||
return
|
||||
try:
|
||||
audio_48k = np.frombuffer(indata, dtype=np.int16)
|
||||
audio_8k = self.resampler.resample48_to_8(audio_48k)
|
||||
|
||||
audio_8k_level_adjusted = audio.set_audio_volume(audio_8k, self.rx_audio_level)
|
||||
|
||||
if not self.states.isTransmitting():
|
||||
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
|
||||
# selected datachannel mode is not full
|
||||
index = 0
|
||||
for mode in self.demodulator.MODE_DICT:
|
||||
mode_data = self.demodulator.MODE_DICT[mode]
|
||||
audiobuffer = mode_data['audio_buffer']
|
||||
decode = mode_data['decode']
|
||||
index += 1
|
||||
if audiobuffer:
|
||||
if (audiobuffer.nbuffer + length_audio_8k_level_adjusted) > audiobuffer.size:
|
||||
self.demodulator.buffer_overflow_counter[index] += 1
|
||||
self.event_manager.send_buffer_overflow(self.demodulator.buffer_overflow_counter)
|
||||
elif decode:
|
||||
audiobuffer.push(audio_8k_level_adjusted)
|
||||
except Exception as e:
|
||||
self.log.warning("[AUDIO EXCEPTION]", status=status, time=time, frames=frames, e=e)
|
||||
|
||||
|
|
150
modem/modulator.py
Normal file
150
modem/modulator.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
import ctypes
|
||||
import codec2
|
||||
import structlog
|
||||
|
||||
|
||||
class Modulator:
|
||||
log = structlog.get_logger("RF")
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.tx_delay = config['MODEM']['tx_delay']
|
||||
self.modem_sample_rate = codec2.api.FREEDV_FS_8000
|
||||
|
||||
# Initialize codec2, rig control, and data threads
|
||||
self.init_codec2()
|
||||
|
||||
def init_codec2(self):
|
||||
# Open codec2 instances
|
||||
|
||||
# INIT TX MODES - here we need all modes.
|
||||
self.freedv_datac0_tx = codec2.open_instance(codec2.FREEDV_MODE.datac0.value)
|
||||
self.freedv_datac1_tx = codec2.open_instance(codec2.FREEDV_MODE.datac1.value)
|
||||
self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.value)
|
||||
self.freedv_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value)
|
||||
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
|
||||
|
||||
def transmit_add_preamble(self, buffer, freedv):
|
||||
# Init buffer for preample
|
||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||
freedv
|
||||
)
|
||||
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
||||
|
||||
# Write preamble to txbuffer
|
||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||
buffer += bytes(mod_out_preamble)
|
||||
return buffer
|
||||
|
||||
def transmit_add_postamble(self, buffer, freedv):
|
||||
# Init buffer for postamble
|
||||
n_tx_postamble_modem_samples = (
|
||||
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||
)
|
||||
mod_out_postamble = ctypes.create_string_buffer(
|
||||
n_tx_postamble_modem_samples * 2
|
||||
)
|
||||
# Write postamble to txbuffer
|
||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||
# Append postamble to txbuffer
|
||||
buffer += bytes(mod_out_postamble)
|
||||
return buffer
|
||||
|
||||
def transmit_add_silence(self, buffer, duration):
|
||||
data_delay = int(self.modem_sample_rate * (duration / 1000)) # type: ignore
|
||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
buffer += bytes(mod_out_silence)
|
||||
return buffer
|
||||
|
||||
def transmit_create_frame(self, txbuffer, freedv, frame):
|
||||
# Get number of bytes per frame for mode
|
||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||
payload_bytes_per_frame = bytes_per_frame - 2
|
||||
|
||||
# Init buffer for data
|
||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
||||
|
||||
# Create buffer for data
|
||||
# Use this if CRC16 checksum is required (DATAc1-3)
|
||||
buffer = bytearray(payload_bytes_per_frame)
|
||||
# Set buffersize to length of data which will be send
|
||||
buffer[: len(frame)] = frame # type: ignore
|
||||
|
||||
# Create crc for data frame -
|
||||
# Use the crc function shipped with codec2
|
||||
# to avoid CRC algorithm incompatibilities
|
||||
# Generate CRC16
|
||||
crc = ctypes.c_ushort(
|
||||
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
|
||||
)
|
||||
# Convert crc to 2-byte (16-bit) hex string
|
||||
crc = crc.value.to_bytes(2, byteorder="big")
|
||||
# Append CRC to data buffer
|
||||
buffer += crc
|
||||
|
||||
assert (bytes_per_frame == len(buffer))
|
||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||
# modulate DATA and save it into mod_out pointer
|
||||
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||
txbuffer += bytes(mod_out)
|
||||
return txbuffer
|
||||
|
||||
def create_burst(
|
||||
self, mode, repeats: int, repeat_delay: int, frames: bytearray
|
||||
) -> bool:
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
repeats:
|
||||
repeat_delay:
|
||||
frames:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# get freedv instance by mode
|
||||
mode_transition = {
|
||||
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
||||
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
||||
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
||||
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
||||
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
||||
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
||||
}
|
||||
if mode in mode_transition:
|
||||
freedv = mode_transition[mode]
|
||||
else:
|
||||
print("wrong mode.................")
|
||||
print(mode)
|
||||
return False
|
||||
|
||||
|
||||
# Open codec2 instance
|
||||
self.MODE = mode
|
||||
self.log.debug(
|
||||
"[MDM] TRANSMIT", mode=self.MODE.name, delay=self.tx_delay
|
||||
)
|
||||
|
||||
txbuffer = bytes()
|
||||
|
||||
# Add empty data to handle ptt toggle time
|
||||
if self.tx_delay > 0:
|
||||
txbuffer = self.transmit_add_silence(txbuffer, self.tx_delay)
|
||||
|
||||
if not isinstance(frames, list): frames = [frames]
|
||||
for _ in range(repeats):
|
||||
|
||||
# Create modulation for all frames in the list
|
||||
for frame in frames:
|
||||
txbuffer = self.transmit_add_preamble(txbuffer, freedv)
|
||||
txbuffer = self.transmit_create_frame(txbuffer, freedv, frame)
|
||||
txbuffer = self.transmit_add_postamble(txbuffer, freedv)
|
||||
|
||||
# Add delay to end of frames
|
||||
txbuffer = self.transmit_add_silence(txbuffer, repeat_delay)
|
||||
|
||||
return txbuffer
|
||||
|
|
@ -16,13 +16,12 @@ class ScheduleManager:
|
|||
self.state_manager = state_manger
|
||||
self.event_manager = event_manager
|
||||
self.config = self.config_manager.read()
|
||||
self.beacon_interval = self.config['MODEM']['beacon_interval']
|
||||
|
||||
self.scheduler = sched.scheduler(time.time, time.sleep)
|
||||
self.events = {
|
||||
'check_for_queued_messages': {'function': self.check_for_queued_messages, 'interval': 10},
|
||||
'explorer_publishing': {'function': self.push_to_explorer, 'interval': 60},
|
||||
'transmitting_beacon': {'function': self.transmit_beacon, 'interval': self.beacon_interval},
|
||||
'transmitting_beacon': {'function': self.transmit_beacon, 'interval': 600},
|
||||
'beacon_cleanup': {'function': self.delete_beacons, 'interval': 600},
|
||||
}
|
||||
self.running = False # Flag to control the running state
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import time
|
||||
|
||||
from flask import Flask, request, jsonify, make_response, abort, Response
|
||||
from flask_sock import Sock
|
||||
from flask_cors import CORS
|
||||
|
@ -13,12 +15,15 @@ import json
|
|||
import websocket_manager as wsm
|
||||
import api_validations as validations
|
||||
import command_cq
|
||||
import command_beacon
|
||||
import command_ping
|
||||
import command_feq
|
||||
import command_test
|
||||
import command_arq_raw
|
||||
import command_message_send
|
||||
import event_manager
|
||||
import atexit
|
||||
|
||||
from message_system_db_manager import DatabaseManager
|
||||
from message_system_db_messages import DatabaseManagerMessages
|
||||
from message_system_db_attachments import DatabaseManagerAttachments
|
||||
|
@ -26,10 +31,9 @@ from message_system_db_beacon import DatabaseManagerBeacon
|
|||
from schedule_manager import ScheduleManager
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
sock = Sock(app)
|
||||
MODEM_VERSION = "0.14.1-alpha"
|
||||
MODEM_VERSION = "0.14.3-alpha"
|
||||
|
||||
# set config file to use
|
||||
def set_config():
|
||||
|
@ -150,6 +154,8 @@ def post_beacon():
|
|||
|
||||
if not app.state_manager.is_beacon_running:
|
||||
app.state_manager.set('is_beacon_running', request.json['enabled'])
|
||||
if not app.state_manager.getARQ():
|
||||
enqueue_tx_command(command_beacon.BeaconCommand, request.json)
|
||||
else:
|
||||
app.state_manager.set('is_beacon_running', request.json['enabled'])
|
||||
|
||||
|
@ -318,7 +324,18 @@ def sock_fft(sock):
|
|||
def sock_states(sock):
|
||||
wsm.handle_connection(sock, wsm.states_client_list, app.state_queue)
|
||||
|
||||
|
||||
@atexit.register
|
||||
def stop_server():
|
||||
try:
|
||||
app.service_manager.stop_modem()
|
||||
if app.service_manager.modem:
|
||||
app.service_manager.modem.sd_input_stream.stop
|
||||
audio.sd._terminate()
|
||||
except Exception as e:
|
||||
print("Error stopping modem")
|
||||
time.sleep(1)
|
||||
print("------------------------------------------")
|
||||
print('Server shutdown...')
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
|
||||
|
@ -355,4 +372,5 @@ if __name__ == "__main__":
|
|||
modemaddress = '127.0.0.1'
|
||||
if not modemport:
|
||||
modemport = 5000
|
||||
|
||||
app.run(modemaddress, modemport)
|
||||
|
|
Loading…
Reference in a new issue