mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge pull request #704 from DJ2LS/dev-datac14
WIP: New speed levels & compression & signalling mode
This commit is contained in:
commit
eab145fc8f
52 changed files with 913 additions and 311 deletions
1
.github/workflows/build_server.yml
vendored
1
.github/workflows/build_server.yml
vendored
|
@ -48,7 +48,6 @@ jobs:
|
||||||
brew install portaudio
|
brew install portaudio
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip3 install pyaudio
|
pip3 install pyaudio
|
||||||
export PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.11/lib/:$PYTHONPATH
|
|
||||||
|
|
||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "FreeDATA",
|
"name": "FreeDATA",
|
||||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.14.5-alpha",
|
"version": "0.15.2-alpha",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
|
|
@ -105,6 +105,8 @@ Spectrum.prototype.drawSpectrum = function () {
|
||||||
var linePositionHigh = 178.4; //150 + bandwidth/20
|
var linePositionHigh = 178.4; //150 + bandwidth/20
|
||||||
var linePositionLow2 = 65; //150 - bandwith/20
|
var linePositionLow2 = 65; //150 - bandwith/20
|
||||||
var linePositionHigh2 = 235; //150 + bandwith/20
|
var linePositionHigh2 = 235; //150 + bandwith/20
|
||||||
|
var linePositionLow3 = 28.1; //150 - bandwith/20
|
||||||
|
var linePositionHigh3 = 271.9; //150 + bandwith/20
|
||||||
this.ctx_wf.beginPath();
|
this.ctx_wf.beginPath();
|
||||||
this.ctx_wf.moveTo(linePositionLow, 0);
|
this.ctx_wf.moveTo(linePositionLow, 0);
|
||||||
this.ctx_wf.lineTo(linePositionLow, height);
|
this.ctx_wf.lineTo(linePositionLow, height);
|
||||||
|
@ -114,6 +116,10 @@ Spectrum.prototype.drawSpectrum = function () {
|
||||||
this.ctx_wf.lineTo(linePositionLow2, height);
|
this.ctx_wf.lineTo(linePositionLow2, height);
|
||||||
this.ctx_wf.moveTo(linePositionHigh2, 0);
|
this.ctx_wf.moveTo(linePositionHigh2, 0);
|
||||||
this.ctx_wf.lineTo(linePositionHigh2, height);
|
this.ctx_wf.lineTo(linePositionHigh2, height);
|
||||||
|
this.ctx_wf.moveTo(linePositionLow3, 0);
|
||||||
|
this.ctx_wf.lineTo(linePositionLow3, height);
|
||||||
|
this.ctx_wf.moveTo(linePositionHigh3, 0);
|
||||||
|
this.ctx_wf.lineTo(linePositionHigh3, height);
|
||||||
this.ctx_wf.lineWidth = 1;
|
this.ctx_wf.lineWidth = 1;
|
||||||
this.ctx_wf.strokeStyle = "#C3C3C3";
|
this.ctx_wf.strokeStyle = "#C3C3C3";
|
||||||
this.ctx_wf.stroke();
|
this.ctx_wf.stroke();
|
||||||
|
@ -454,7 +460,7 @@ export function Spectrum(id, options) {
|
||||||
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
|
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
|
||||||
this.spanHz = options && options.spanHz ? options.spanHz : 0;
|
this.spanHz = options && options.spanHz ? options.spanHz : 0;
|
||||||
this.wf_size = options && options.wf_size ? options.wf_size : 0;
|
this.wf_size = options && options.wf_size ? options.wf_size : 0;
|
||||||
this.wf_rows = options && options.wf_rows ? options.wf_rows : 1024;
|
this.wf_rows = options && options.wf_rows ? options.wf_rows : 512;
|
||||||
this.spectrumPercent =
|
this.spectrumPercent =
|
||||||
options && options.spectrumPercent ? options.spectrumPercent : 0;
|
options && options.spectrumPercent ? options.spectrumPercent : 0;
|
||||||
this.spectrumPercentStep =
|
this.spectrumPercentStep =
|
||||||
|
|
|
@ -32,8 +32,7 @@ import grid_freq from "./grid/grid_frequency.vue";
|
||||||
import grid_beacon from "./grid/grid_beacon.vue";
|
import grid_beacon from "./grid/grid_beacon.vue";
|
||||||
import grid_mycall_small from "./grid/grid_mycall small.vue";
|
import grid_mycall_small from "./grid/grid_mycall small.vue";
|
||||||
import grid_scatter from "./grid/grid_scatter.vue";
|
import grid_scatter from "./grid/grid_scatter.vue";
|
||||||
import { stateDispatcher } from "../js/eventHandler";
|
import grid_stats_chart from "./grid/grid_stats_chart.vue";
|
||||||
import { Scatter } from "vue-chartjs";
|
|
||||||
|
|
||||||
let count = ref(0);
|
let count = ref(0);
|
||||||
let grid = null; // DO NOT use ref(null) as proxies GS will break all logic when comparing structures... see https://github.com/gridstack/gridstack.js/issues/2115
|
let grid = null; // DO NOT use ref(null) as proxies GS will break all logic when comparing structures... see https://github.com/gridstack/gridstack.js/issues/2115
|
||||||
|
@ -63,7 +62,8 @@ class gridWidget {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Array of grid widgets, do not change array order as it'll affect saved configs
|
//Array of grid widgets
|
||||||
|
//Order can be changed so sorted correctly, but do not change ID as it'll affect saved configs
|
||||||
const gridWidgets = [
|
const gridWidgets = [
|
||||||
new gridWidget(
|
new gridWidget(
|
||||||
grid_activities,
|
grid_activities,
|
||||||
|
@ -247,8 +247,16 @@ new gridWidget(
|
||||||
"Stats",
|
"Stats",
|
||||||
19,
|
19,
|
||||||
),
|
),
|
||||||
|
new gridWidget(
|
||||||
//New new widget ID should be 20
|
grid_stats_chart,
|
||||||
|
{ x: 0, y: 114, w: 6, h: 30 },
|
||||||
|
"Speed/SNR graph",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"Stats",
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
//Next new widget ID should be 21
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,22 @@ function transmitPing() {
|
||||||
|
|
||||||
function startStopBeacon() {
|
function startStopBeacon() {
|
||||||
if (state.beacon_state === true) {
|
if (state.beacon_state === true) {
|
||||||
setModemBeacon(false);
|
setModemBeacon(false, state.away_from_key);
|
||||||
} else {
|
} else {
|
||||||
setModemBeacon(true);
|
setModemBeacon(true, state.away_from_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAwayFromKey(){
|
||||||
|
if (state.away_from_key === true) {
|
||||||
|
setModemBeacon(state.beacon_state, false);
|
||||||
|
} else {
|
||||||
|
setModemBeacon(state.beacon_state, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var dxcallPing = ref("");
|
var dxcallPing = ref("");
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"stationSelected",
|
"stationSelected",
|
||||||
|
@ -39,7 +50,7 @@ window.addEventListener(
|
||||||
<div class="card-body overflow-auto p-0">
|
<div class="card-body overflow-auto p-0">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<div class="row mb-2 mt-2">
|
<div class="row mb-2 mt-2">
|
||||||
<div class="col-sm-8">
|
<div class="col">
|
||||||
<div class="input-group w-100">
|
<div class="input-group w-100">
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input
|
<input
|
||||||
|
@ -65,25 +76,10 @@ window.addEventListener(
|
||||||
title="Send a ping request to a remote station"
|
title="Send a ping request to a remote station"
|
||||||
@click="transmitPing()"
|
@click="transmitPing()"
|
||||||
>
|
>
|
||||||
<strong>Ping</strong>
|
<strong>PING Station</strong>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
role="switch"
|
|
||||||
id="flexSwitchBeacon"
|
|
||||||
v-model="state.beacon_state"
|
|
||||||
@click="startStopBeacon()"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="flexSwitchBeacon"
|
|
||||||
>Beacon</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -99,6 +95,41 @@ window.addEventListener(
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
id="flexSwitchBeacon"
|
||||||
|
v-model="state.beacon_state"
|
||||||
|
@click="startStopBeacon()"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="flexSwitchBeacon"
|
||||||
|
>Enable Beacon</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
id="flexSwitchAFK"
|
||||||
|
v-model="state.away_from_key"
|
||||||
|
@click="setAwayFromKey()"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="flexSwitchAFK"
|
||||||
|
>Away From Key</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,9 +34,10 @@ function getMaidenheadDistance(dxGrid) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pushToPing(origin)
|
function pushToPing(origin) {
|
||||||
{
|
window.dispatchEvent(
|
||||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
new CustomEvent("stationSelected", { bubbles: true, detail: origin }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -61,11 +62,16 @@ function pushToPing(origin)
|
||||||
<th scope="col" id="thType">Type</th>
|
<th scope="col" id="thType">Type</th>
|
||||||
<th scope="col" id="thSnr">SNR</th>
|
<th scope="col" id="thSnr">SNR</th>
|
||||||
<!--<th scope="col">Off</th>-->
|
<!--<th scope="col">Off</th>-->
|
||||||
|
<th scope="col" id="thSnr">AFK?</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="gridHeardStations">
|
<tbody id="gridHeardStations">
|
||||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
<tr
|
||||||
|
v-for="item in state.heard_stations"
|
||||||
|
:key="item.origin"
|
||||||
|
@click="pushToPing(item.origin)"
|
||||||
|
>
|
||||||
<td>
|
<td>
|
||||||
{{ getDateTime(item.timestamp) }}
|
{{ getDateTime(item.timestamp) }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -83,6 +89,9 @@ function pushToPing(origin)
|
||||||
<td>
|
<td>
|
||||||
{{ item.snr }}
|
{{ item.snr }}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ item.away_from_key }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// @ts-nocheck
|
// @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
|
// 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 { ref, computed, onMounted, nextTick } from "vue";
|
import { ref, computed, onMounted, nextTick, toRaw } from "vue";
|
||||||
import { initWaterfall, setColormap } from "../../js/waterfallHandler.js";
|
import { initWaterfall, setColormap } from "../../js/waterfallHandler.js";
|
||||||
import { setActivePinia } from "pinia";
|
import { setActivePinia } from "pinia";
|
||||||
import pinia from "../../store/index";
|
import pinia from "../../store/index";
|
||||||
|
@ -89,7 +89,7 @@ const transmissionSpeedChartData = computed(() => ({
|
||||||
{
|
{
|
||||||
type: "line",
|
type: "line",
|
||||||
label: "SNR[dB]",
|
label: "SNR[dB]",
|
||||||
data: state.arq_speed_list_snr,
|
data: state.arq_speed_list_snr.value,
|
||||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||||
pointRadius: 1,
|
pointRadius: 1,
|
||||||
segment: {
|
segment: {
|
||||||
|
@ -106,7 +106,7 @@ const transmissionSpeedChartData = computed(() => ({
|
||||||
{
|
{
|
||||||
type: "bar",
|
type: "bar",
|
||||||
label: "Speed[bpm]",
|
label: "Speed[bpm]",
|
||||||
data: state.arq_speed_list_bpm,
|
data: state.arq_speed_list_bpm.value,
|
||||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||||
order: 0,
|
order: 0,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useStateStore } from "../../store/stateStore.js";
|
||||||
const state = useStateStore(pinia);
|
const state = useStateStore(pinia);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
<div class="progress mb-0 rounded-0 rounded-top" style="height: 22px">
|
||||||
<div
|
<div
|
||||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||||
id="dbfs_level"
|
id="dbfs_level"
|
||||||
|
@ -24,7 +24,7 @@ const state = useStateStore(pinia);
|
||||||
{{ state.dbfs_level }} dBFS
|
{{ state.dbfs_level }} dBFS
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
<div class="progress mb-0 rounded-0 rounded-bottom" style="height: 8px">
|
||||||
<div
|
<div
|
||||||
class="progress-bar progress-bar-striped bg-warning"
|
class="progress-bar progress-bar-striped bg-warning"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useStateStore } from "../../store/stateStore.js";
|
||||||
const state = useStateStore(pinia);
|
const state = useStateStore(pinia);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
<div class="progress mb-0 rounded-0 rounded-top" style="height: 22px">
|
||||||
<div
|
<div
|
||||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||||
id="noise_level"
|
id="noise_level"
|
||||||
|
@ -24,7 +24,7 @@ const state = useStateStore(pinia);
|
||||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
<div class="progress mb-0 rounded-0 rounded-bottom" style="height: 8px">
|
||||||
<div
|
<div
|
||||||
class="progress-bar progress-bar-striped bg-warning"
|
class="progress-bar progress-bar-striped bg-warning"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
|
|
105
gui/src/components/grid/grid_stats_chart.vue
Normal file
105
gui/src/components/grid/grid_stats_chart.vue
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<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 { ref, computed, onMounted, nextTick, toRaw } from "vue";
|
||||||
|
import { setActivePinia } from "pinia";
|
||||||
|
import pinia from "../../store/index";
|
||||||
|
setActivePinia(pinia);
|
||||||
|
|
||||||
|
import { useStateStore } from "../../store/stateStore.js";
|
||||||
|
const state = useStateStore(pinia);
|
||||||
|
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from "chart.js";
|
||||||
|
import { Line, Scatter } from "vue-chartjs";
|
||||||
|
|
||||||
|
|
||||||
|
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.value,
|
||||||
|
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.value,
|
||||||
|
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||||
|
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||||
|
order: 0,
|
||||||
|
yAxisID: "SPEED",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Line
|
||||||
|
:data="transmissionSpeedChartData"
|
||||||
|
:options="transmissionSpeedChartOptions"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -4,7 +4,7 @@ import pinia from "../../store/index";
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
<div class="fill h-100">
|
||||||
<a
|
<a
|
||||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||||
data-bs-trigger="hover"
|
data-bs-trigger="hover"
|
||||||
|
|
|
@ -175,6 +175,7 @@ const audioStore = useAudioStore();
|
||||||
<option value="250">250 Hz</option>
|
<option value="250">250 Hz</option>
|
||||||
<option value="563">563 Hz</option>
|
<option value="563">563 Hz</option>
|
||||||
<option value="1700">1700 Hz</option>
|
<option value="1700">1700 Hz</option>
|
||||||
|
<option value="2438">2438 Hz</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -92,8 +92,11 @@ export async function getSerialDevices() {
|
||||||
return await apiGet("/devices/serial");
|
return await apiGet("/devices/serial");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setModemBeacon(enabled = false) {
|
export async function setModemBeacon(enabled = false, away_from_key = false) {
|
||||||
return await apiPost("/modem/beacon", { enabled: enabled });
|
return await apiPost("/modem/beacon", {
|
||||||
|
enabled: enabled,
|
||||||
|
away_from_key: away_from_key,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendModemCQ() {
|
export async function sendModemCQ() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
setStateFailed,
|
setStateFailed,
|
||||||
} from "./chatHandler";
|
} from "./chatHandler";
|
||||||
*/
|
*/
|
||||||
|
import { toRaw } from "vue";
|
||||||
import { displayToast } from "./popupHandler";
|
import { displayToast } from "./popupHandler";
|
||||||
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
|
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
|
||||||
import { processFreedataMessages } from "./messagesHandler.ts";
|
import { processFreedataMessages } from "./messagesHandler.ts";
|
||||||
|
@ -29,8 +30,11 @@ import {
|
||||||
getRemote,
|
getRemote,
|
||||||
} from "../store/settingsStore.js";
|
} from "../store/settingsStore.js";
|
||||||
|
|
||||||
export function loadAllData() {
|
export async function loadAllData() {
|
||||||
getModemState();
|
// TODO: Make this working
|
||||||
|
let stateData = await getModemState();
|
||||||
|
console.log(stateData);
|
||||||
|
|
||||||
getRemote();
|
getRemote();
|
||||||
getOverallHealth();
|
getOverallHealth();
|
||||||
audioStore.loadAudioDevices();
|
audioStore.loadAudioDevices();
|
||||||
|
@ -66,7 +70,10 @@ export function stateDispatcher(data) {
|
||||||
);
|
);
|
||||||
|
|
||||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||||
|
|
||||||
stateStore.beacon_state = data["is_beacon_running"];
|
stateStore.beacon_state = data["is_beacon_running"];
|
||||||
|
stateStore.is_away_from_key = data["is_away_from_key"];
|
||||||
|
|
||||||
stateStore.radio_status = data["radio_status"];
|
stateStore.radio_status = data["radio_status"];
|
||||||
stateStore.frequency = data["radio_frequency"];
|
stateStore.frequency = data["radio_frequency"];
|
||||||
stateStore.mode = data["radio_mode"];
|
stateStore.mode = data["radio_mode"];
|
||||||
|
@ -178,12 +185,16 @@ export function eventDispatcher(data) {
|
||||||
100;
|
100;
|
||||||
stateStore.arq_total_bytes =
|
stateStore.arq_total_bytes =
|
||||||
data["arq-transfer-outbound"].received_bytes;
|
data["arq-transfer-outbound"].received_bytes;
|
||||||
stateStore.arq_speed_list_timestamp =
|
stateStore.arq_speed_list_timestamp.value = toRaw(
|
||||||
data["arq-transfer-outbound"].statistics.time_histogram;
|
data["arq-transfer-outbound"].statistics.time_histogram,
|
||||||
stateStore.arq_speed_list_bpm =
|
);
|
||||||
data["arq-transfer-outbound"].statistics.bpm_histogram;
|
stateStore.arq_speed_list_bpm.value = toRaw(
|
||||||
stateStore.arq_speed_list_snr =
|
data["arq-transfer-outbound"].statistics.bpm_histogram,
|
||||||
data["arq-transfer-outbound"].statistics.snr_histogram;
|
);
|
||||||
|
stateStore.arq_speed_list_snr.value = toRaw(
|
||||||
|
data["arq-transfer-outbound"].statistics.snr_histogram,
|
||||||
|
);
|
||||||
|
//console.log(toRaw(stateStore.arq_speed_list_timestamp.value));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "ABORTING":
|
case "ABORTING":
|
||||||
|
@ -226,13 +237,12 @@ export function eventDispatcher(data) {
|
||||||
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
|
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
|
||||||
stateStore.arq_transmission_percent = 0;
|
stateStore.arq_transmission_percent = 0;
|
||||||
stateStore.arq_total_bytes = 0;
|
stateStore.arq_total_bytes = 0;
|
||||||
stateStore.arq_speed_list_timestamp =
|
//stateStore.arq_speed_list_timestamp =
|
||||||
data["arq-transfer-inbound"].statistics.time_histogram;
|
// [];
|
||||||
stateStore.arq_speed_list_bpm =
|
//stateStore.arq_speed_list_bpm =
|
||||||
data["arq-transfer-inbound"].statistics.bpm_histogram;
|
// [];
|
||||||
stateStore.arq_speed_list_snr =
|
//stateStore.arq_speed_list_snr =
|
||||||
data["arq-transfer-inbound"].statistics.snr_histogram;
|
// [];
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "OPEN_ACK_SENT":
|
case "OPEN_ACK_SENT":
|
||||||
|
@ -266,6 +276,19 @@ export function eventDispatcher(data) {
|
||||||
100;
|
100;
|
||||||
stateStore.arq_total_bytes =
|
stateStore.arq_total_bytes =
|
||||||
data["arq-transfer-inbound"].received_bytes;
|
data["arq-transfer-inbound"].received_bytes;
|
||||||
|
//console.log(data["arq-transfer-inbound"].statistics.time_histogram);
|
||||||
|
stateStore.arq_speed_list_timestamp.value = toRaw(
|
||||||
|
data["arq-transfer-inbound"].statistics.time_histogram,
|
||||||
|
);
|
||||||
|
stateStore.arq_speed_list_bpm.value = toRaw(
|
||||||
|
data["arq-transfer-inbound"].statistics.bpm_histogram,
|
||||||
|
);
|
||||||
|
stateStore.arq_speed_list_snr.value = toRaw(
|
||||||
|
data["arq-transfer-inbound"].statistics.snr_histogram,
|
||||||
|
);
|
||||||
|
console.log(stateStore.arq_speed_list_timestamp.value);
|
||||||
|
console.log(stateStore.arq_speed_list_bpm.value);
|
||||||
|
console.log(stateStore.arq_speed_list_snr.value);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "ENDED":
|
case "ENDED":
|
||||||
|
|
|
@ -38,6 +38,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
||||||
var arq_session_state = ref("");
|
var arq_session_state = ref("");
|
||||||
var arq_state = ref("");
|
var arq_state = ref("");
|
||||||
var beacon_state = ref(false);
|
var beacon_state = ref(false);
|
||||||
|
var away_from_key = ref(false);
|
||||||
|
|
||||||
var audio_recording = ref(false);
|
var audio_recording = ref(false);
|
||||||
|
|
||||||
|
@ -115,6 +116,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
||||||
activities,
|
activities,
|
||||||
heard_stations,
|
heard_stations,
|
||||||
beacon_state,
|
beacon_state,
|
||||||
|
away_from_key,
|
||||||
rigctld_started,
|
rigctld_started,
|
||||||
rigctld_process,
|
rigctld_process,
|
||||||
python_version,
|
python_version,
|
||||||
|
|
3
modem/.gitignore
vendored
3
modem/.gitignore
vendored
|
@ -130,3 +130,6 @@ dmypy.json
|
||||||
|
|
||||||
# FreeDATA config
|
# FreeDATA config
|
||||||
config.ini
|
config.ini
|
||||||
|
|
||||||
|
#FreeData DB
|
||||||
|
freedata-messages.db
|
|
@ -3,6 +3,7 @@
|
||||||
import structlog
|
import structlog
|
||||||
import lzma
|
import lzma
|
||||||
import gzip
|
import gzip
|
||||||
|
import zlib
|
||||||
from message_p2p import message_received, message_failed, message_transmitted
|
from message_p2p import message_received, message_failed, message_transmitted
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ class ARQ_SESSION_TYPES(Enum):
|
||||||
raw = 0
|
raw = 0
|
||||||
raw_lzma = 10
|
raw_lzma = 10
|
||||||
raw_gzip = 11
|
raw_gzip = 11
|
||||||
p2pmsg_lzma = 20
|
p2pmsg_zlib = 20
|
||||||
p2p_connection = 30
|
p2p_connection = 30
|
||||||
|
|
||||||
class ARQDataTypeHandler:
|
class ARQDataTypeHandler:
|
||||||
|
@ -38,11 +39,11 @@ class ARQDataTypeHandler:
|
||||||
'failed': self.failed_raw_gzip,
|
'failed': self.failed_raw_gzip,
|
||||||
'transmitted': self.transmitted_raw_gzip,
|
'transmitted': self.transmitted_raw_gzip,
|
||||||
},
|
},
|
||||||
ARQ_SESSION_TYPES.p2pmsg_lzma: {
|
ARQ_SESSION_TYPES.p2pmsg_zlib: {
|
||||||
'prepare': self.prepare_p2pmsg_lzma,
|
'prepare': self.prepare_p2pmsg_zlib,
|
||||||
'handle': self.handle_p2pmsg_lzma,
|
'handle': self.handle_p2pmsg_zlib,
|
||||||
'failed' : self.failed_p2pmsg_lzma,
|
'failed' : self.failed_p2pmsg_zlib,
|
||||||
'transmitted': self.transmitted_p2pmsg_lzma,
|
'transmitted': self.transmitted_p2pmsg_zlib,
|
||||||
},
|
},
|
||||||
ARQ_SESSION_TYPES.p2p_connection: {
|
ARQ_SESSION_TYPES.p2p_connection: {
|
||||||
'prepare': self.prepare_p2p_connection,
|
'prepare': self.prepare_p2p_connection,
|
||||||
|
@ -148,25 +149,39 @@ class ARQDataTypeHandler:
|
||||||
decompressed_data = gzip.decompress(data)
|
decompressed_data = gzip.decompress(data)
|
||||||
return decompressed_data
|
return decompressed_data
|
||||||
|
|
||||||
def prepare_p2pmsg_lzma(self, data):
|
def prepare_p2pmsg_zlib(self, data):
|
||||||
compressed_data = lzma.compress(data)
|
compressed_data = lzma.compress(data)
|
||||||
self.log(f"Preparing LZMA compressed P2PMSG data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
|
||||||
|
compressor = zlib.compressobj(level=6, wbits=-zlib.MAX_WBITS, strategy=zlib.Z_FILTERED)
|
||||||
|
compressed_data = compressor.compress(data) + compressor.flush()
|
||||||
|
|
||||||
|
self.log(f"Preparing ZLIB compressed P2PMSG data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
||||||
return compressed_data
|
return compressed_data
|
||||||
|
|
||||||
def handle_p2pmsg_lzma(self, data, statistics):
|
def handle_p2pmsg_zlib(self, data, statistics):
|
||||||
decompressed_data = lzma.decompress(data)
|
decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
|
||||||
self.log(f"Handling LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
decompressed_data = decompressor.decompress(data)
|
||||||
|
decompressed_data += decompressor.flush()
|
||||||
|
|
||||||
|
self.log(f"Handling ZLIB compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||||
message_received(self.event_manager, self.state_manager, decompressed_data, statistics)
|
message_received(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||||
return decompressed_data
|
return decompressed_data
|
||||||
|
|
||||||
def failed_p2pmsg_lzma(self, data, statistics):
|
def failed_p2pmsg_zlib(self, data, statistics):
|
||||||
decompressed_data = lzma.decompress(data)
|
decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
|
||||||
self.log(f"Handling failed LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
|
decompressed_data = decompressor.decompress(data)
|
||||||
|
decompressed_data += decompressor.flush()
|
||||||
|
|
||||||
|
self.log(f"Handling failed ZLIB compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
|
||||||
message_failed(self.event_manager, self.state_manager, decompressed_data, statistics)
|
message_failed(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||||
return decompressed_data
|
return decompressed_data
|
||||||
|
|
||||||
def transmitted_p2pmsg_lzma(self, data, statistics):
|
def transmitted_p2pmsg_zlib(self, data, statistics):
|
||||||
decompressed_data = lzma.decompress(data)
|
# Create a decompression object with the same wbits setting used for compression
|
||||||
|
decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
|
||||||
|
decompressed_data = decompressor.decompress(data)
|
||||||
|
decompressed_data += decompressor.flush()
|
||||||
|
|
||||||
message_transmitted(self.event_manager, self.state_manager, decompressed_data, statistics)
|
message_transmitted(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||||
return decompressed_data
|
return decompressed_data
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,18 @@ class ARQSession:
|
||||||
'duration_per_frame': 4.18,
|
'duration_per_frame': 4.18,
|
||||||
'bandwidth': 1700,
|
'bandwidth': 1700,
|
||||||
},
|
},
|
||||||
|
3: {
|
||||||
|
'mode': codec2.FREEDV_MODE.data_ofdm_2438,
|
||||||
|
'min_snr': 8,
|
||||||
|
'duration_per_frame': 6.5,
|
||||||
|
'bandwidth': 2438,
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
'mode': codec2.FREEDV_MODE.qam16c2,
|
||||||
|
'min_snr': 11,
|
||||||
|
'duration_per_frame': 2.8,
|
||||||
|
'bandwidth': 2438,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config: dict, modem, dxcall: str, state_manager):
|
def __init__(self, config: dict, modem, dxcall: str, state_manager):
|
||||||
|
@ -41,6 +53,8 @@ class ARQSession:
|
||||||
self.states = state_manager
|
self.states = state_manager
|
||||||
self.states.setARQ(True)
|
self.states.setARQ(True)
|
||||||
|
|
||||||
|
self.protocol_version = 1
|
||||||
|
|
||||||
self.snr = []
|
self.snr = []
|
||||||
|
|
||||||
self.dxcall = dxcall
|
self.dxcall = dxcall
|
||||||
|
@ -152,6 +166,7 @@ class ARQSession:
|
||||||
}
|
}
|
||||||
|
|
||||||
def update_histograms(self, confirmed_bytes, total_bytes):
|
def update_histograms(self, confirmed_bytes, total_bytes):
|
||||||
|
|
||||||
stats = self.calculate_session_statistics(confirmed_bytes, total_bytes)
|
stats = self.calculate_session_statistics(confirmed_bytes, total_bytes)
|
||||||
self.snr_histogram.append(self.snr)
|
self.snr_histogram.append(self.snr)
|
||||||
self.bpm_histogram.append(stats['bytes_per_minute'])
|
self.bpm_histogram.append(stats['bytes_per_minute'])
|
||||||
|
|
|
@ -81,6 +81,7 @@ class ARQSessionIRS(arq_session.ARQSession):
|
||||||
self.abort = False
|
self.abort = False
|
||||||
|
|
||||||
def all_data_received(self):
|
def all_data_received(self):
|
||||||
|
print(f"{self.total_length} vs {self.received_bytes}")
|
||||||
return self.total_length == self.received_bytes
|
return self.total_length == self.received_bytes
|
||||||
|
|
||||||
def final_crc_matches(self) -> bool:
|
def final_crc_matches(self) -> bool:
|
||||||
|
@ -108,11 +109,18 @@ class ARQSessionIRS(arq_session.ARQSession):
|
||||||
|
|
||||||
self.event_manager.send_arq_session_new(
|
self.event_manager.send_arq_session_new(
|
||||||
False, self.id, self.dxcall, 0, self.state.name)
|
False, self.id, self.dxcall, 0, self.state.name)
|
||||||
|
|
||||||
|
if open_frame['protocol_version'] not in [self.protocol_version]:
|
||||||
|
self.abort = True
|
||||||
|
self.log(f"Protocol version mismatch! Setting disconnect flag!", isWarning=True)
|
||||||
|
self.set_state(IRS_State.ABORTED)
|
||||||
|
|
||||||
ack_frame = self.frame_factory.build_arq_session_open_ack(
|
ack_frame = self.frame_factory.build_arq_session_open_ack(
|
||||||
self.id,
|
self.id,
|
||||||
self.dxcall,
|
self.dxcall,
|
||||||
self.version,
|
self.version,
|
||||||
self.snr, flag_abort=self.abort)
|
self.snr, flag_abort=self.abort)
|
||||||
|
|
||||||
self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
||||||
if not self.abort:
|
if not self.abort:
|
||||||
self.set_state(IRS_State.OPEN_ACK_SENT)
|
self.set_state(IRS_State.OPEN_ACK_SENT)
|
||||||
|
@ -140,12 +148,14 @@ class ARQSessionIRS(arq_session.ARQSession):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def process_incoming_data(self, frame):
|
def process_incoming_data(self, frame):
|
||||||
|
print(frame)
|
||||||
if frame['offset'] != self.received_bytes:
|
if frame['offset'] != self.received_bytes:
|
||||||
self.log(f"Discarding data offset {frame['offset']}")
|
# TODO: IF WE HAVE AN OFFSET BECAUSE OF A SPEED LEVEL CHANGE FOR EXAMPLE,
|
||||||
return False
|
# TODO: WE HAVE TO DISCARD THE LAST BYTES, BUT NOT returning False!!
|
||||||
|
self.log(f"Discarding data offset {frame['offset']} vs {self.received_bytes}", isWarning=True)
|
||||||
|
#return False
|
||||||
|
|
||||||
remaining_data_length = self.total_length - self.received_bytes
|
remaining_data_length = self.total_length - self.received_bytes
|
||||||
|
|
||||||
# Is this the last data part?
|
# Is this the last data part?
|
||||||
if remaining_data_length <= len(frame['data']):
|
if remaining_data_length <= len(frame['data']):
|
||||||
# we only want the remaining length, not the entire frame data
|
# we only want the remaining length, not the entire frame data
|
||||||
|
@ -155,7 +165,8 @@ class ARQSessionIRS(arq_session.ARQSession):
|
||||||
data_part = frame['data']
|
data_part = frame['data']
|
||||||
|
|
||||||
self.received_data[frame['offset']:] = data_part
|
self.received_data[frame['offset']:] = data_part
|
||||||
self.received_bytes += len(data_part)
|
#self.received_bytes += len(data_part)
|
||||||
|
self.received_bytes = len(self.received_data)
|
||||||
self.log(f"Received {self.received_bytes}/{self.total_length} bytes")
|
self.log(f"Received {self.received_bytes}/{self.total_length} bytes")
|
||||||
self.event_manager.send_arq_session_progress(
|
self.event_manager.send_arq_session_progress(
|
||||||
False, self.id, self.dxcall, self.received_bytes, self.total_length, self.state.name, self.calculate_session_statistics(self.received_bytes, self.total_length))
|
False, self.id, self.dxcall, self.received_bytes, self.total_length, self.state.name, self.calculate_session_statistics(self.received_bytes, self.total_length))
|
||||||
|
@ -171,41 +182,35 @@ class ARQSessionIRS(arq_session.ARQSession):
|
||||||
self.calibrate_speed_settings(burst_frame=burst_frame)
|
self.calibrate_speed_settings(burst_frame=burst_frame)
|
||||||
ack = self.frame_factory.build_arq_burst_ack(
|
ack = self.frame_factory.build_arq_burst_ack(
|
||||||
self.id,
|
self.id,
|
||||||
self.received_bytes,
|
|
||||||
self.speed_level,
|
self.speed_level,
|
||||||
self.frames_per_burst,
|
|
||||||
self.snr,
|
|
||||||
flag_abort=self.abort
|
flag_abort=self.abort
|
||||||
)
|
)
|
||||||
|
|
||||||
self.set_state(IRS_State.BURST_REPLY_SENT)
|
self.set_state(IRS_State.BURST_REPLY_SENT)
|
||||||
self.launch_transmit_and_wait(ack, self.TIMEOUT_DATA, mode=FREEDV_MODE.signalling)
|
self.event_manager.send_arq_session_progress(False, self.id, self.dxcall, self.received_bytes,
|
||||||
|
self.total_length, self.state.name,
|
||||||
|
statistics=self.calculate_session_statistics(
|
||||||
|
self.received_bytes, self.total_length))
|
||||||
|
|
||||||
|
self.launch_transmit_and_wait(ack, self.TIMEOUT_DATA, mode=FREEDV_MODE.signalling_ack)
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
if self.final_crc_matches():
|
if self.final_crc_matches():
|
||||||
self.log("All data received successfully!")
|
self.log("All data received successfully!")
|
||||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||||
self.received_bytes,
|
|
||||||
self.speed_level,
|
self.speed_level,
|
||||||
self.frames_per_burst,
|
|
||||||
self.snr,
|
|
||||||
flag_final=True,
|
flag_final=True,
|
||||||
flag_checksum=True)
|
flag_checksum=True)
|
||||||
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
self.transmit_frame(ack, mode=FREEDV_MODE.signalling_ack)
|
||||||
self.log("ACK sent")
|
self.log("ACK sent")
|
||||||
self.session_ended = time.time()
|
self.session_ended = time.time()
|
||||||
self.set_state(IRS_State.ENDED)
|
self.set_state(IRS_State.ENDED)
|
||||||
self.event_manager.send_arq_session_finished(
|
|
||||||
False, self.id, self.dxcall, True, self.state.name, data=self.received_data, statistics=self.calculate_session_statistics(self.received_bytes, self.total_length))
|
|
||||||
|
|
||||||
return self.received_data, self.type_byte
|
return self.received_data, self.type_byte
|
||||||
else:
|
else:
|
||||||
|
|
||||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||||
self.received_bytes,
|
|
||||||
self.speed_level,
|
self.speed_level,
|
||||||
self.frames_per_burst,
|
|
||||||
self.snr,
|
|
||||||
flag_final=True,
|
flag_final=True,
|
||||||
flag_checksum=False)
|
flag_checksum=False)
|
||||||
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
|
|
||||||
# DJ2LS: 3 seconds seems to be too small for radios with a too slow PTT toggle time
|
# DJ2LS: 3 seconds seems to be too small for radios with a too slow PTT toggle time
|
||||||
# DJ2LS: 3.5 seconds is working well WITHOUT a channel busy detection delay
|
# DJ2LS: 3.5 seconds is working well WITHOUT a channel busy detection delay
|
||||||
TIMEOUT_CHANNEL_BUSY = 2
|
TIMEOUT_CHANNEL_BUSY = 0
|
||||||
TIMEOUT_CONNECT_ACK = 3.5 + TIMEOUT_CHANNEL_BUSY
|
TIMEOUT_CONNECT_ACK = 3.5 + TIMEOUT_CHANNEL_BUSY
|
||||||
TIMEOUT_TRANSFER = 3.5 + TIMEOUT_CHANNEL_BUSY
|
TIMEOUT_TRANSFER = 3.5 + TIMEOUT_CHANNEL_BUSY
|
||||||
TIMEOUT_STOP_ACK = 3.5 + TIMEOUT_CHANNEL_BUSY
|
TIMEOUT_STOP_ACK = 3.5 + TIMEOUT_CHANNEL_BUSY
|
||||||
|
@ -60,6 +60,7 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
self.data_crc = ''
|
self.data_crc = ''
|
||||||
self.type_byte = type_byte
|
self.type_byte = type_byte
|
||||||
self.confirmed_bytes = 0
|
self.confirmed_bytes = 0
|
||||||
|
self.expected_byte_offset = 0
|
||||||
|
|
||||||
self.state = ISS_State.NEW
|
self.state = ISS_State.NEW
|
||||||
self.state_enum = ISS_State # needed for access State enum from outside
|
self.state_enum = ISS_State # needed for access State enum from outside
|
||||||
|
@ -93,7 +94,9 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
if retries == 8 and isARQBurst and self.speed_level > 0:
|
if retries == 8 and isARQBurst and self.speed_level > 0:
|
||||||
self.log("SENDING IN FALLBACK SPEED LEVEL", isWarning=True)
|
self.log("SENDING IN FALLBACK SPEED LEVEL", isWarning=True)
|
||||||
self.speed_level = 0
|
self.speed_level = 0
|
||||||
self.send_data({'flag':{'ABORT': False, 'FINAL': False}, 'speed_level': self.speed_level})
|
print(f" CONFIRMED BYTES: {self.confirmed_bytes}")
|
||||||
|
self.send_data({'flag':{'ABORT': False, 'FINAL': False}, 'speed_level': self.speed_level}, fallback=True)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.set_state(ISS_State.FAILED)
|
self.set_state(ISS_State.FAILED)
|
||||||
|
@ -105,9 +108,10 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
|
maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
|
||||||
|
print(maximum_bandwidth)
|
||||||
self.event_manager.send_arq_session_new(
|
self.event_manager.send_arq_session_new(
|
||||||
True, self.id, self.dxcall, self.total_length, self.state.name)
|
True, self.id, self.dxcall, self.total_length, self.state.name)
|
||||||
session_open_frame = self.frame_factory.build_arq_session_open(self.dxcall, self.id, maximum_bandwidth)
|
session_open_frame = self.frame_factory.build_arq_session_open(self.dxcall, self.id, maximum_bandwidth, self.protocol_version)
|
||||||
self.launch_twr(session_open_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
|
self.launch_twr(session_open_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
|
||||||
self.set_state(ISS_State.OPEN_SENT)
|
self.set_state(ISS_State.OPEN_SENT)
|
||||||
|
|
||||||
|
@ -136,8 +140,7 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
def send_info(self, irs_frame):
|
def send_info(self, irs_frame):
|
||||||
# check if we received an abort flag
|
# check if we received an abort flag
|
||||||
if irs_frame["flag"]["ABORT"]:
|
if irs_frame["flag"]["ABORT"]:
|
||||||
self.transmission_aborted(irs_frame)
|
return self.transmission_aborted(irs_frame)
|
||||||
return
|
|
||||||
|
|
||||||
info_frame = self.frame_factory.build_arq_session_info(self.id, self.total_length,
|
info_frame = self.frame_factory.build_arq_session_info(self.id, self.total_length,
|
||||||
helpers.get_crc_32(self.data),
|
helpers.get_crc_32(self.data),
|
||||||
|
@ -148,16 +151,26 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def send_data(self, irs_frame):
|
def send_data(self, irs_frame, fallback=None):
|
||||||
|
|
||||||
|
# interrupt transmission when aborting
|
||||||
|
if self.state in [ISS_State.ABORTED, ISS_State.ABORTING]:
|
||||||
|
self.event_frame_received.set()
|
||||||
|
self.send_stop()
|
||||||
|
return
|
||||||
|
|
||||||
# update statistics
|
# update statistics
|
||||||
self.update_histograms(self.confirmed_bytes, self.total_length)
|
self.update_histograms(self.confirmed_bytes, self.total_length)
|
||||||
|
|
||||||
self.update_speed_level(irs_frame)
|
self.update_speed_level(irs_frame)
|
||||||
if 'offset' in irs_frame:
|
|
||||||
self.confirmed_bytes = irs_frame['offset']
|
|
||||||
|
if self.expected_byte_offset > self.total_length:
|
||||||
|
self.confirmed_bytes = self.total_length
|
||||||
|
elif not fallback:
|
||||||
|
self.confirmed_bytes = self.expected_byte_offset
|
||||||
|
|
||||||
self.log(f"IRS confirmed {self.confirmed_bytes}/{self.total_length} bytes")
|
self.log(f"IRS confirmed {self.confirmed_bytes}/{self.total_length} bytes")
|
||||||
self.event_manager.send_arq_session_progress(
|
self.event_manager.send_arq_session_progress(True, self.id, self.dxcall, self.confirmed_bytes, self.total_length, self.state.name, statistics=self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
||||||
True, self.id, self.dxcall, self.confirmed_bytes, self.total_length, self.state.name, statistics=self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
|
||||||
|
|
||||||
# check if we received an abort flag
|
# check if we received an abort flag
|
||||||
if irs_frame["flag"]["ABORT"]:
|
if irs_frame["flag"]["ABORT"]:
|
||||||
|
@ -176,10 +189,14 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
burst = []
|
burst = []
|
||||||
for _ in range(0, self.frames_per_burst):
|
for _ in range(0, self.frames_per_burst):
|
||||||
offset = self.confirmed_bytes
|
offset = self.confirmed_bytes
|
||||||
|
#self.expected_byte_offset = offset
|
||||||
payload = self.data[offset : offset + payload_size]
|
payload = self.data[offset : offset + payload_size]
|
||||||
|
#self.expected_byte_offset = offset + payload_size
|
||||||
|
self.expected_byte_offset = offset + len(payload)
|
||||||
|
#print(f"EXPECTED----------------------{self.expected_byte_offset}")
|
||||||
data_frame = self.frame_factory.build_arq_burst_frame(
|
data_frame = self.frame_factory.build_arq_burst_frame(
|
||||||
self.SPEED_LEVEL_DICT[self.speed_level]["mode"],
|
self.SPEED_LEVEL_DICT[self.speed_level]["mode"],
|
||||||
self.id, self.confirmed_bytes, payload, self.speed_level)
|
self.id, offset, payload, self.speed_level)
|
||||||
burst.append(data_frame)
|
burst.append(data_frame)
|
||||||
self.launch_twr(burst, self.TIMEOUT_TRANSFER, self.RETRIES_CONNECT, mode='auto', isARQBurst=True)
|
self.launch_twr(burst, self.TIMEOUT_TRANSFER, self.RETRIES_CONNECT, mode='auto', isARQBurst=True)
|
||||||
self.set_state(ISS_State.BURST_SENT)
|
self.set_state(ISS_State.BURST_SENT)
|
||||||
|
@ -192,8 +209,8 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
self.log(f"All data transfered! flag_final={irs_frame['flag']['FINAL']}, flag_checksum={irs_frame['flag']['CHECKSUM']}")
|
self.log(f"All data transfered! flag_final={irs_frame['flag']['FINAL']}, flag_checksum={irs_frame['flag']['CHECKSUM']}")
|
||||||
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,True, self.state.name, statistics=self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,True, self.state.name, statistics=self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
||||||
|
|
||||||
print(self.state_manager.p2p_connection_sessions)
|
#print(self.state_manager.p2p_connection_sessions)
|
||||||
print(self.arq_data_type_handler.state_manager.p2p_connection_sessions)
|
#print(self.arq_data_type_handler.state_manager.p2p_connection_sessions)
|
||||||
|
|
||||||
self.arq_data_type_handler.transmitted(self.type_byte, self.data, self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
self.arq_data_type_handler.transmitted(self.type_byte, self.data, self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
||||||
self.state_manager.remove_arq_iss_session(self.id)
|
self.state_manager.remove_arq_iss_session(self.id)
|
||||||
|
@ -222,9 +239,6 @@ class ARQSessionISS(arq_session.ARQSession):
|
||||||
# break actual retries
|
# break actual retries
|
||||||
self.event_frame_received.set()
|
self.event_frame_received.set()
|
||||||
|
|
||||||
# start with abort sequence
|
|
||||||
self.send_stop()
|
|
||||||
|
|
||||||
def send_stop(self):
|
def send_stop(self):
|
||||||
stop_frame = self.frame_factory.build_arq_stop(self.id)
|
stop_frame = self.frame_factory.build_arq_stop(self.id)
|
||||||
self.launch_twr(stop_frame, self.TIMEOUT_STOP_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
|
self.launch_twr(stop_frame, self.TIMEOUT_STOP_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
|
||||||
|
|
|
@ -210,6 +210,36 @@ def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray:
|
||||||
RMS_COUNTER = 0
|
RMS_COUNTER = 0
|
||||||
CHANNEL_BUSY_DELAY = 0
|
CHANNEL_BUSY_DELAY = 0
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_data_for_fft(data, target_length_samples=400):
|
||||||
|
"""
|
||||||
|
Prepare data array for FFT by padding if necessary to match the target length.
|
||||||
|
Center the data if it's shorter than the target length.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- data: numpy array of np.int16, representing the input data.
|
||||||
|
- target_length_samples: int, the target length of the data in samples.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- numpy array of np.int16, padded and/or centered if necessary.
|
||||||
|
"""
|
||||||
|
# Calculate the current length in samples
|
||||||
|
current_length_samples = data.size
|
||||||
|
|
||||||
|
# Check if padding is needed
|
||||||
|
if current_length_samples < target_length_samples:
|
||||||
|
# Calculate total padding needed
|
||||||
|
total_pad_length = target_length_samples - current_length_samples
|
||||||
|
# Calculate padding on each side
|
||||||
|
pad_before = total_pad_length // 2
|
||||||
|
pad_after = total_pad_length - pad_before
|
||||||
|
# Pad the data to center it
|
||||||
|
data_padded = np.pad(data, (pad_before, pad_after), 'constant', constant_values=(0,))
|
||||||
|
return data_padded
|
||||||
|
else:
|
||||||
|
# No padding needed, return original data
|
||||||
|
return data
|
||||||
|
|
||||||
def calculate_fft(data, fft_queue, states) -> None:
|
def calculate_fft(data, fft_queue, states) -> None:
|
||||||
"""
|
"""
|
||||||
Calculate an average signal strength of the channel to assess
|
Calculate an average signal strength of the channel to assess
|
||||||
|
@ -225,6 +255,7 @@ def calculate_fft(data, fft_queue, states) -> None:
|
||||||
global RMS_COUNTER, CHANNEL_BUSY_DELAY
|
global RMS_COUNTER, CHANNEL_BUSY_DELAY
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
data = prepare_data_for_fft(data, target_length_samples=800)
|
||||||
fftarray = np.fft.rfft(data)
|
fftarray = np.fft.rfft(data)
|
||||||
|
|
||||||
# Set value 0 to 1 to avoid division by zero
|
# Set value 0 to 1 to avoid division by zero
|
||||||
|
@ -321,6 +352,8 @@ def calculate_fft(data, fft_queue, states) -> None:
|
||||||
# erase queue if greater than 3
|
# erase queue if greater than 3
|
||||||
if fft_queue.qsize() >= 1:
|
if fft_queue.qsize() >= 1:
|
||||||
fft_queue = queue.Queue()
|
fft_queue = queue.Queue()
|
||||||
fft_queue.put(dfftlist[:315]) # 315 --> bandwidth 3200
|
#fft_queue.put(dfftlist[:315]) # 315 --> bandwidth 3200
|
||||||
|
fft_queue.put(dfftlist) # 315 --> bandwidth 3200
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"[MDM] calculate_fft: Exception: {err}")
|
print(f"[MDM] calculate_fft: Exception: {err}")
|
||||||
|
|
366
modem/codec2.py
366
modem/codec2.py
|
@ -7,6 +7,8 @@ Python interface to the C-language codec2 library.
|
||||||
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
|
from ctypes import *
|
||||||
|
import hashlib
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -25,12 +27,17 @@ class FREEDV_MODE(Enum):
|
||||||
Enumeration for codec2 modes and names
|
Enumeration for codec2 modes and names
|
||||||
"""
|
"""
|
||||||
signalling = 19
|
signalling = 19
|
||||||
|
signalling_ack = 20
|
||||||
datac0 = 14
|
datac0 = 14
|
||||||
datac1 = 10
|
datac1 = 10
|
||||||
datac3 = 12
|
datac3 = 12
|
||||||
datac4 = 18
|
datac4 = 18
|
||||||
datac13 = 19
|
datac13 = 19
|
||||||
|
datac14 = 20
|
||||||
|
data_ofdm_500 = 21500
|
||||||
|
data_ofdm_2438 = 2124381
|
||||||
|
#data_qam_2438 = 2124382
|
||||||
|
qam16c2 = 22
|
||||||
|
|
||||||
class FREEDV_MODE_USED_SLOTS(Enum):
|
class FREEDV_MODE_USED_SLOTS(Enum):
|
||||||
"""
|
"""
|
||||||
|
@ -43,9 +50,11 @@ class FREEDV_MODE_USED_SLOTS(Enum):
|
||||||
datac3 = [False, False, True, False, False]
|
datac3 = [False, False, True, False, False]
|
||||||
datac4 = [False, False, True, False, False]
|
datac4 = [False, False, True, False, False]
|
||||||
datac13 = [False, False, True, False, False]
|
datac13 = [False, False, True, False, False]
|
||||||
fsk_ldpc = [False, False, True, False, False]
|
datac14 = [False, False, True, False, False]
|
||||||
fsk_ldpc_0 = [False, False, True, False, False]
|
data_ofdm_500 = [False, False, True, False, False]
|
||||||
fsk_ldpc_1 = [False, False, True, False, False]
|
data_ofdm_2438 = [True, True, True, True, True]
|
||||||
|
data_qam_2438 = [True, True, True, True, True]
|
||||||
|
qam16c2 = [True, True, True, True, True]
|
||||||
|
|
||||||
# Function for returning the mode value
|
# Function for returning the mode value
|
||||||
def freedv_get_mode_value_by_name(mode: str) -> int:
|
def freedv_get_mode_value_by_name(mode: str) -> int:
|
||||||
|
@ -90,7 +99,6 @@ elif sys.platform in ["win32", "win64"]:
|
||||||
files = glob.glob(os.path.join(script_dir, "**\\*libcodec2*.dll"), recursive=True)
|
files = glob.glob(os.path.join(script_dir, "**\\*libcodec2*.dll"), recursive=True)
|
||||||
else:
|
else:
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
api = None
|
api = None
|
||||||
for file in files:
|
for file in files:
|
||||||
try:
|
try:
|
||||||
|
@ -105,7 +113,7 @@ for file in files:
|
||||||
if api is None or "api" not in locals():
|
if api is None or "api" not in locals():
|
||||||
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
|
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
log.info("[C2 ] Libcodec2 loaded...")
|
#log.info("[C2 ] Libcodec2 loaded...", path=file)
|
||||||
# ctypes function init
|
# ctypes function init
|
||||||
|
|
||||||
# api.freedv_set_tuning_range.restype = ctypes.c_int
|
# api.freedv_set_tuning_range.restype = ctypes.c_int
|
||||||
|
@ -167,64 +175,6 @@ api.freedv_get_n_max_modem_samples.restype = ctypes.c_int
|
||||||
|
|
||||||
api.FREEDV_FS_8000 = 8000 # type: ignore
|
api.FREEDV_FS_8000 = 8000 # type: ignore
|
||||||
|
|
||||||
# -------------------------------- FSK LDPC MODE SETTINGS
|
|
||||||
|
|
||||||
|
|
||||||
class ADVANCED(ctypes.Structure):
|
|
||||||
"""Advanced structure for fsk modes"""
|
|
||||||
|
|
||||||
_fields_ = [
|
|
||||||
("interleave_frames", ctypes.c_int),
|
|
||||||
("M", ctypes.c_int),
|
|
||||||
("Rs", ctypes.c_int),
|
|
||||||
("Fs", ctypes.c_int),
|
|
||||||
("first_tone", ctypes.c_int),
|
|
||||||
("tone_spacing", ctypes.c_int),
|
|
||||||
("codename", ctypes.c_char_p),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=pointless-string-statement
|
|
||||||
"""
|
|
||||||
adv.interleave_frames = 0 # max amplitude
|
|
||||||
adv.M = 2 # number of fsk tones 2/4
|
|
||||||
adv.Rs = 100 # symbol rate
|
|
||||||
adv.Fs = 8000 # sample rate
|
|
||||||
adv.first_tone = 1500 # first tone freq
|
|
||||||
adv.tone_spacing = 200 # shift between tones
|
|
||||||
adv.codename = "H_128_256_5".encode("utf-8") # code word
|
|
||||||
|
|
||||||
HRA_112_112 rate 0.50 (224,112) BPF: 14 not working
|
|
||||||
HRA_56_56 rate 0.50 (112,56) BPF: 7 not working
|
|
||||||
H_2064_516_sparse rate 0.80 (2580,2064) BPF: 258 working
|
|
||||||
HRAb_396_504 rate 0.79 (504,396) BPF: 49 not working
|
|
||||||
H_256_768_22 rate 0.33 (768,256) BPF: 32 working
|
|
||||||
H_256_512_4 rate 0.50 (512,256) BPF: 32 working
|
|
||||||
HRAa_1536_512 rate 0.75 (2048,1536) BPF: 192 not working
|
|
||||||
H_128_256_5 rate 0.50 (256,128) BPF: 16 working
|
|
||||||
H_4096_8192_3d rate 0.50 (8192,4096) BPF: 512 not working
|
|
||||||
H_16200_9720 rate 0.60 (16200,9720) BPF: 1215 not working
|
|
||||||
H_1024_2048_4f rate 0.50 (2048,1024) BPF: 128 working
|
|
||||||
"""
|
|
||||||
# --------------- 2 FSK H_128_256_5, 16 bytes
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV = ADVANCED() # type: ignore
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.interleave_frames = 0
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.M = 4
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.Rs = 500
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.Fs = 8000
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.first_tone = 1150 # 1150 4fsk, 1500 2fsk
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.tone_spacing = 200 # 200
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_0_ADV.codename = "H_128_256_5".encode("utf-8") # code word
|
|
||||||
|
|
||||||
# --------------- 4 H_256_512_4, 7 bytes
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV = ADVANCED() # type: ignore
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.interleave_frames = 0
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.M = 4
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.Rs = 1000
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.Fs = 8000
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.first_tone = 1150 # 1250 4fsk, 1500 2fsk
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.tone_spacing = 200
|
|
||||||
api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = "H_4096_8192_3d".encode("utf-8") # code word
|
|
||||||
|
|
||||||
# ------- MODEM STATS STRUCTURES
|
# ------- MODEM STATS STRUCTURES
|
||||||
MODEM_STATS_NC_MAX = 50 + 1 * 2
|
MODEM_STATS_NC_MAX = 50 + 1 * 2
|
||||||
|
@ -236,6 +186,8 @@ MODEM_STATS_MAX_F_HZ = 4000
|
||||||
MODEM_STATS_MAX_F_EST = 4
|
MODEM_STATS_MAX_F_EST = 4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MODEMSTATS(ctypes.Structure):
|
class MODEMSTATS(ctypes.Structure):
|
||||||
"""Modem statistics structure"""
|
"""Modem statistics structure"""
|
||||||
|
|
||||||
|
@ -421,34 +373,22 @@ class resampler:
|
||||||
return out48
|
return out48
|
||||||
|
|
||||||
def open_instance(mode: int) -> ctypes.c_void_p:
|
def open_instance(mode: int) -> ctypes.c_void_p:
|
||||||
"""
|
data_custom = 21
|
||||||
Return a codec2 instance of the type `mode`
|
if mode in [FREEDV_MODE.data_ofdm_500.value, FREEDV_MODE.data_ofdm_2438.value]:
|
||||||
|
#if mode in [FREEDV_MODE.data_ofdm_500.value, FREEDV_MODE.data_ofdm_2438.value, FREEDV_MODE.data_qam_2438]:
|
||||||
:param mode: Type of codec2 instance to return
|
custom_params = ofdm_configurations[mode]
|
||||||
:type mode: Union[int, str]
|
return ctypes.cast(
|
||||||
:return: C-function of the requested codec2 instance
|
api.freedv_open_advanced(
|
||||||
:rtype: ctypes.c_void_p
|
data_custom,
|
||||||
"""
|
ctypes.byref(custom_params),
|
||||||
# if mode in [FREEDV_MODE.fsk_ldpc_0.value]:
|
),
|
||||||
# return ctypes.cast(
|
ctypes.c_void_p,
|
||||||
# api.freedv_open_advanced(
|
)
|
||||||
# FREEDV_MODE.fsk_ldpc.value,
|
else:
|
||||||
# ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
if mode not in [data_custom]:
|
||||||
# ),
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# if mode in [FREEDV_MODE.fsk_ldpc_1.value]:
|
|
||||||
# return ctypes.cast(
|
|
||||||
# api.freedv_open_advanced(
|
|
||||||
# FREEDV_MODE.fsk_ldpc.value,
|
|
||||||
# ctypes.byref(api.FREEDV_MODE_FSK_LDPC_1_ADV),
|
|
||||||
# ),
|
|
||||||
# ctypes.c_void_p,
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
||||||
|
|
||||||
|
|
||||||
def get_bytes_per_frame(mode: int) -> int:
|
def get_bytes_per_frame(mode: int) -> int:
|
||||||
"""
|
"""
|
||||||
Provide bytes per frame information for accessing from data handler
|
Provide bytes per frame information for accessing from data handler
|
||||||
|
@ -462,3 +402,249 @@ def get_bytes_per_frame(mode: int) -> int:
|
||||||
# TODO add close session
|
# TODO add close session
|
||||||
# get number of bytes per frame for mode
|
# get number of bytes per frame for mode
|
||||||
return int(api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
return int(api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
|
|
||||||
|
|
||||||
|
MAX_UW_BITS = 192
|
||||||
|
|
||||||
|
class OFDM_CONFIG(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("tx_centre", ctypes.c_float), # TX Centre Audio Frequency
|
||||||
|
("rx_centre", ctypes.c_float), # RX Centre Audio Frequency
|
||||||
|
("fs", ctypes.c_float), # Sample Frequency
|
||||||
|
("rs", ctypes.c_float), # Symbol Rate
|
||||||
|
("ts", ctypes.c_float), # Symbol duration
|
||||||
|
("tcp", ctypes.c_float), # Cyclic Prefix duration
|
||||||
|
("timing_mx_thresh", ctypes.c_float), # Threshold for timing metrics
|
||||||
|
("nc", ctypes.c_int), # Number of carriers
|
||||||
|
("ns", ctypes.c_int), # Number of Symbol frames
|
||||||
|
("np", ctypes.c_int), # Number of modem frames per packet
|
||||||
|
("bps", ctypes.c_int), # Bits per Symbol
|
||||||
|
("txtbits", ctypes.c_int), # Number of auxiliary data bits
|
||||||
|
("nuwbits", ctypes.c_int), # Number of unique word bits
|
||||||
|
("bad_uw_errors", ctypes.c_int), # Threshold for bad unique word detection
|
||||||
|
("ftwindowwidth", ctypes.c_int), # Filter window width
|
||||||
|
("edge_pilots", ctypes.c_int), # Edge pilots configuration
|
||||||
|
("state_machine", ctypes.c_char_p), # Name of sync state machine used
|
||||||
|
("codename", ctypes.c_char_p), # LDPC codename
|
||||||
|
("tx_uw", ctypes.c_uint8 * MAX_UW_BITS), # User defined unique word
|
||||||
|
("amp_est_mode", ctypes.c_int), # Amplitude estimator algorithm mode
|
||||||
|
("tx_bpf_en", ctypes.c_bool), # TX BPF enable flag
|
||||||
|
("rx_bpf_en", ctypes.c_bool), # RX BPF enable flag
|
||||||
|
("foff_limiter", ctypes.c_bool), # Frequency offset limiter enable flag
|
||||||
|
("amp_scale", ctypes.c_float), # Amplitude scale factor
|
||||||
|
("clip_gain1", ctypes.c_float), # Pre-clipping gain
|
||||||
|
("clip_gain2", ctypes.c_float), # Post-clipping gain
|
||||||
|
("clip_en", ctypes.c_bool), # Clipping enable flag
|
||||||
|
("mode", ctypes.c_char * 16), # OFDM mode in string form
|
||||||
|
("data_mode", ctypes.c_char_p), # Data mode ("streaming", "burst", etc.)
|
||||||
|
("fmin", ctypes.c_float), # Minimum frequency for tuning range
|
||||||
|
("fmax", ctypes.c_float), # Maximum frequency for tuning range
|
||||||
|
("EsNodB", ctypes.c_float),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FREEDV_ADVANCED(ctypes.Structure):
|
||||||
|
"""Advanced structure for fsk and ofdm modes"""
|
||||||
|
_fields_ = [
|
||||||
|
("interleave_frames", ctypes.c_int),
|
||||||
|
("M", ctypes.c_int),
|
||||||
|
("Rs", ctypes.c_int),
|
||||||
|
("Fs", ctypes.c_int),
|
||||||
|
("first_tone", ctypes.c_int),
|
||||||
|
("tone_spacing", ctypes.c_int),
|
||||||
|
("codename", ctypes.c_char_p),
|
||||||
|
("config", ctypes.POINTER(OFDM_CONFIG))
|
||||||
|
]
|
||||||
|
|
||||||
|
api.freedv_open_advanced.argtypes = [ctypes.c_int, ctypes.POINTER(FREEDV_ADVANCED)]
|
||||||
|
api.freedv_open_advanced.restype = ctypes.c_void_p
|
||||||
|
|
||||||
|
def create_default_ofdm_config():
|
||||||
|
uw_sequence = (c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS))
|
||||||
|
|
||||||
|
ofdm_default_config = OFDM_CONFIG(
|
||||||
|
tx_centre=1500.0,
|
||||||
|
rx_centre=1500.0,
|
||||||
|
fs=8000.0,
|
||||||
|
rs=62.5,
|
||||||
|
ts=0.016,
|
||||||
|
tcp=0.006,
|
||||||
|
timing_mx_thresh=0.10,
|
||||||
|
nc=9,
|
||||||
|
ns=5,
|
||||||
|
np=29,
|
||||||
|
bps=2,
|
||||||
|
txtbits=0,
|
||||||
|
nuwbits=40,
|
||||||
|
bad_uw_errors=10,
|
||||||
|
ftwindowwidth=80,
|
||||||
|
edge_pilots=False,
|
||||||
|
state_machine="data".encode("utf-8"),
|
||||||
|
codename="H_1024_2048_4f".encode("utf-8"),
|
||||||
|
tx_uw=uw_sequence,
|
||||||
|
amp_est_mode=1,
|
||||||
|
tx_bpf_en=False,
|
||||||
|
rx_bpf_en=False,
|
||||||
|
foff_limiter=False,
|
||||||
|
amp_scale=300E3,
|
||||||
|
clip_gain1=2.2,
|
||||||
|
clip_gain2=0.8,
|
||||||
|
clip_en=False,
|
||||||
|
mode=b"CUSTOM",
|
||||||
|
data_mode=b"streaming",
|
||||||
|
fmin=-50.0,
|
||||||
|
fmax=50.0,
|
||||||
|
EsNodB=3.0,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
return FREEDV_ADVANCED(
|
||||||
|
interleave_frames = 0,
|
||||||
|
M = 2,
|
||||||
|
Rs = 100,
|
||||||
|
Fs = 8000,
|
||||||
|
first_tone = 1000,
|
||||||
|
tone_spacing = 200,
|
||||||
|
codename = "H_256_512_4".encode("utf-8"),
|
||||||
|
config = ctypes.pointer(ofdm_default_config),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_tx_uw(nuwbits, uw_sequence):
|
||||||
|
"""
|
||||||
|
Creates a tx_uw ctypes array filled with the uw_sequence up to nuwbits.
|
||||||
|
If uw_sequence is shorter than nuwbits, the rest of the array is filled with zeros.
|
||||||
|
|
||||||
|
:param nuwbits: The number of bits for the tx_uw array, should not exceed MAX_UW_BITS.
|
||||||
|
:param uw_sequence: List of integers representing the unique word sequence.
|
||||||
|
:return: A ctypes array representing the tx_uw.
|
||||||
|
"""
|
||||||
|
# Ensure nuwbits does not exceed MAX_UW_BITS
|
||||||
|
if nuwbits > MAX_UW_BITS:
|
||||||
|
raise ValueError(f"nuwbits exceeds MAX_UW_BITS: {MAX_UW_BITS}")
|
||||||
|
|
||||||
|
tx_uw_array = (ctypes.c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS))
|
||||||
|
for i in range(min(len(uw_sequence), MAX_UW_BITS)):
|
||||||
|
tx_uw_array[i] = uw_sequence[i]
|
||||||
|
|
||||||
|
return tx_uw_array
|
||||||
|
|
||||||
|
"""
|
||||||
|
# DATAC1
|
||||||
|
data_ofdm_500_config = create_default_ofdm_config()
|
||||||
|
data_ofdm_500_config.config.contents.ns = 5
|
||||||
|
data_ofdm_500_config.config.contents.np = 38
|
||||||
|
data_ofdm_500_config.config.contents.tcp = 0.006
|
||||||
|
data_ofdm_500_config.config.contents.ts = 0.016
|
||||||
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts
|
||||||
|
data_ofdm_500_config.config.contents.nc = 27
|
||||||
|
data_ofdm_500_config.config.contents.nuwbits = 16
|
||||||
|
data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_500_config.config.contents.bad_uw_errors = 6
|
||||||
|
data_ofdm_500_config.config.contents.codename = b"H_4096_8192_3d"
|
||||||
|
data_ofdm_500_config.config.contents.amp_scale = 145E3
|
||||||
|
data_ofdm_500_config.config.contents.tx_uw = create_tx_uw(16, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0])
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
# DATAC3
|
||||||
|
data_ofdm_500_config = create_default_ofdm_config()
|
||||||
|
data_ofdm_500_config.config.contents.ns = 5
|
||||||
|
data_ofdm_500_config.config.contents.np = 29
|
||||||
|
data_ofdm_500_config.config.contents.tcp = 0.006
|
||||||
|
data_ofdm_500_config.config.contents.ts = 0.016
|
||||||
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts
|
||||||
|
data_ofdm_500_config.config.contents.nc = 9
|
||||||
|
data_ofdm_500_config.config.contents.nuwbits = 40
|
||||||
|
data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_500_config.config.contents.bad_uw_errors = 10
|
||||||
|
data_ofdm_500_config.config.contents.codename = b"H_1024_2048_4f"
|
||||||
|
data_ofdm_500_config.config.contents.clip_gain1 = 2.2
|
||||||
|
data_ofdm_500_config.config.contents.clip_gain2 = 0.8
|
||||||
|
data_ofdm_500_config.config.contents.tx_uw = create_tx_uw(40, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0])
|
||||||
|
|
||||||
|
"""
|
||||||
|
# ---------------- OFDM 500 Hz Bandwidth ---------------#
|
||||||
|
data_ofdm_500_config = create_default_ofdm_config()
|
||||||
|
data_ofdm_500_config.config.contents.ns = 5
|
||||||
|
data_ofdm_500_config.config.contents.np = 38
|
||||||
|
data_ofdm_500_config.config.contents.tcp = 0.006
|
||||||
|
data_ofdm_500_config.config.contents.ts = 0.016
|
||||||
|
data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts
|
||||||
|
data_ofdm_500_config.config.contents.nc = 27
|
||||||
|
data_ofdm_500_config.config.contents.nuwbits = 16
|
||||||
|
data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_500_config.config.contents.bad_uw_errors = 6
|
||||||
|
data_ofdm_500_config.config.contents.codename = b"H_4096_8192_3d"
|
||||||
|
data_ofdm_500_config.config.contents.amp_scale = 145E3
|
||||||
|
data_ofdm_500_config.config.contents.tx_uw = create_tx_uw(16, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- OFDM 2438 Hz Bandwidth 16200,9720 ---------------#
|
||||||
|
data_ofdm_2438_config = create_default_ofdm_config()
|
||||||
|
data_ofdm_2438_config.config.contents.ns = 5
|
||||||
|
data_ofdm_2438_config.config.contents.np = 52
|
||||||
|
data_ofdm_2438_config.config.contents.tcp = 0.004
|
||||||
|
data_ofdm_2438_config.config.contents.ts = 0.016
|
||||||
|
data_ofdm_2438_config.config.contents.rs = 1.0 / data_ofdm_2438_config.config.contents.ts
|
||||||
|
data_ofdm_2438_config.config.contents.nc = 39
|
||||||
|
data_ofdm_2438_config.config.contents.nuwbits = 24
|
||||||
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_2438_config.config.contents.bad_uw_errors = 8
|
||||||
|
data_ofdm_2438_config.config.contents.amp_est_mode = 0
|
||||||
|
data_ofdm_2438_config.config.contents.amp_scale = 135E3
|
||||||
|
data_ofdm_2438_config.config.contents.codename = b"H_16200_9720"
|
||||||
|
data_ofdm_2438_config.config.contents.clip_gain1 = 2.7
|
||||||
|
data_ofdm_2438_config.config.contents.clip_gain2 = 0.8
|
||||||
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_2438_config.config.contents.tx_uw = create_tx_uw(24, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1])
|
||||||
|
|
||||||
|
# ---------------- OFDM 2438 Hz Bandwidth 8192,4096 ---------------#
|
||||||
|
"""
|
||||||
|
data_ofdm_2438_config = create_default_ofdm_config()
|
||||||
|
data_ofdm_2438_config.config.contents.ns = 5
|
||||||
|
data_ofdm_2438_config.config.contents.np = 27
|
||||||
|
data_ofdm_2438_config.config.contents.tcp = 0.005
|
||||||
|
data_ofdm_2438_config.config.contents.ts = 0.018
|
||||||
|
data_ofdm_2438_config.config.contents.rs = 1.0 / data_ofdm_2438_config.config.contents.ts
|
||||||
|
data_ofdm_2438_config.config.contents.nc = 38
|
||||||
|
data_ofdm_2438_config.config.contents.nuwbits = 16
|
||||||
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_2438_config.config.contents.bad_uw_errors = 8
|
||||||
|
data_ofdm_2438_config.config.contents.amp_est_mode = 0
|
||||||
|
data_ofdm_2438_config.config.contents.amp_scale = 145E3
|
||||||
|
data_ofdm_2438_config.config.contents.codename = b"H_4096_8192_3d"
|
||||||
|
data_ofdm_2438_config.config.contents.clip_gain1 = 2.7
|
||||||
|
data_ofdm_2438_config.config.contents.clip_gain2 = 0.8
|
||||||
|
data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_ofdm_2438_config.config.contents.tx_uw = create_tx_uw(16, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1])
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
# ---------------- QAM 2438 Hz Bandwidth ---------------#
|
||||||
|
data_qam_2438_config = create_default_ofdm_config()
|
||||||
|
data_qam_2438_config.config.contents.bps = 4
|
||||||
|
data_qam_2438_config.config.contents.ns = 5
|
||||||
|
data_qam_2438_config.config.contents.np = 26
|
||||||
|
data_qam_2438_config.config.contents.tcp = 0.005
|
||||||
|
data_qam_2438_config.config.contents.ts = 0.018
|
||||||
|
data_qam_2438_config.config.contents.rs = 1.0 / data_qam_2438_config.config.contents.ts
|
||||||
|
data_qam_2438_config.config.contents.nc = 39
|
||||||
|
data_qam_2438_config.config.contents.nuwbits = 162
|
||||||
|
data_qam_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_qam_2438_config.config.contents.bad_uw_errors = 50
|
||||||
|
data_qam_2438_config.config.contents.amp_est_mode = 0
|
||||||
|
data_qam_2438_config.config.contents.amp_scale = 145E3
|
||||||
|
data_qam_2438_config.config.contents.codename = b"H_16200_9720"
|
||||||
|
data_qam_2438_config.config.contents.clip_gain1 = 2.7
|
||||||
|
data_qam_2438_config.config.contents.clip_gain2 = 0.8
|
||||||
|
data_qam_2438_config.config.contents.timing_mx_thresh = 0.10
|
||||||
|
data_qam_2438_config.config.contents.tx_uw = create_tx_uw(162, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0])
|
||||||
|
"""
|
||||||
|
ofdm_configurations = {
|
||||||
|
FREEDV_MODE.data_ofdm_500.value: data_ofdm_500_config,
|
||||||
|
FREEDV_MODE.data_ofdm_2438.value: data_ofdm_2438_config,
|
||||||
|
#FREEDV_MODE.data_qam_2438.value: data_qam_2438_config
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -61,5 +61,4 @@ class TxCommand():
|
||||||
def test(self, event_queue: queue.Queue):
|
def test(self, event_queue: queue.Queue):
|
||||||
self.emit_event(event_queue)
|
self.emit_event(event_queue)
|
||||||
self.logger.info(self.log_message())
|
self.logger.info(self.log_message())
|
||||||
frame = self.build_frame()
|
return self.build_frame()
|
||||||
return frame
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ from command import TxCommand
|
||||||
class BeaconCommand(TxCommand):
|
class BeaconCommand(TxCommand):
|
||||||
|
|
||||||
def build_frame(self):
|
def build_frame(self):
|
||||||
return self.frame_factory.build_beacon()
|
beacon_state = self.state_manager.is_away_from_key
|
||||||
|
return self.frame_factory.build_beacon(beacon_state)
|
||||||
|
|
||||||
|
|
||||||
#def transmit(self, modem):
|
#def transmit(self, modem):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from command import TxCommand
|
from command import TxCommand
|
||||||
|
from codec2 import FREEDV_MODE
|
||||||
class CQCommand(TxCommand):
|
class CQCommand(TxCommand):
|
||||||
|
|
||||||
def build_frame(self):
|
def build_frame(self):
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SendMessageCommand(TxCommand):
|
||||||
# Convert JSON string to bytes (using UTF-8 encoding)
|
# Convert JSON string to bytes (using UTF-8 encoding)
|
||||||
payload = message.to_payload().encode('utf-8')
|
payload = message.to_payload().encode('utf-8')
|
||||||
json_bytearray = bytearray(payload)
|
json_bytearray = bytearray(payload)
|
||||||
data, data_type = self.arq_data_type_handler.prepare(json_bytearray, ARQ_SESSION_TYPES.p2pmsg_lzma)
|
data, data_type = self.arq_data_type_handler.prepare(json_bytearray, ARQ_SESSION_TYPES.p2pmsg_zlib)
|
||||||
|
|
||||||
iss = ARQSessionISS(self.config,
|
iss = ARQSessionISS(self.config,
|
||||||
modem,
|
modem,
|
||||||
|
|
|
@ -48,7 +48,7 @@ enable_hmac = False
|
||||||
enable_morse_identifier = False
|
enable_morse_identifier = False
|
||||||
respond_to_cq = True
|
respond_to_cq = True
|
||||||
tx_delay = 50
|
tx_delay = 50
|
||||||
maximum_bandwidth = 1700
|
maximum_bandwidth = 2375
|
||||||
enable_socket_interface = False
|
enable_socket_interface = False
|
||||||
|
|
||||||
[SOCKET_INTERFACE]
|
[SOCKET_INTERFACE]
|
||||||
|
|
|
@ -94,7 +94,7 @@ class CONFIG:
|
||||||
except Exception:
|
except Exception:
|
||||||
self.config_name = "config.ini"
|
self.config_name = "config.ini"
|
||||||
|
|
||||||
self.log.info("[CFG] config init", file=self.config_name)
|
#self.log.info("[CFG] config init", file=self.config_name)
|
||||||
|
|
||||||
# check if config file exists
|
# check if config file exists
|
||||||
self.config_exists()
|
self.config_exists()
|
||||||
|
|
|
@ -6,6 +6,7 @@ class DataFrameFactory:
|
||||||
|
|
||||||
LENGTH_SIG0_FRAME = 14
|
LENGTH_SIG0_FRAME = 14
|
||||||
LENGTH_SIG1_FRAME = 14
|
LENGTH_SIG1_FRAME = 14
|
||||||
|
LENGTH_ACK_FRAME = 3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
helpers.set_flag(byte, 'DATA-ACK-NACK', True, FLAG_POSITIONS)
|
helpers.set_flag(byte, 'DATA-ACK-NACK', True, FLAG_POSITIONS)
|
||||||
|
@ -17,6 +18,10 @@ class DataFrameFactory:
|
||||||
'CHECKSUM': 2, # Bit-position for indicating the CHECKSUM is correct or not
|
'CHECKSUM': 2, # Bit-position for indicating the CHECKSUM is correct or not
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BEACON_FLAGS = {
|
||||||
|
'AWAY_FROM_KEY': 0, # Bit-position for indicating the AWAY FROM KEY state
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
|
||||||
self.myfullcall = f"{config['STATION']['mycall']}-{config['STATION']['myssid']}"
|
self.myfullcall = f"{config['STATION']['mycall']}-{config['STATION']['myssid']}"
|
||||||
|
@ -27,7 +32,6 @@ class DataFrameFactory:
|
||||||
|
|
||||||
self._load_broadcast_templates()
|
self._load_broadcast_templates()
|
||||||
self._load_ping_templates()
|
self._load_ping_templates()
|
||||||
self._load_fec_templates()
|
|
||||||
self._load_arq_templates()
|
self._load_arq_templates()
|
||||||
self._load_p2p_connection_templates()
|
self._load_p2p_connection_templates()
|
||||||
|
|
||||||
|
@ -51,7 +55,8 @@ class DataFrameFactory:
|
||||||
self.template_list[FR_TYPE.BEACON.value] = {
|
self.template_list[FR_TYPE.BEACON.value] = {
|
||||||
"frame_length": self.LENGTH_SIG0_FRAME,
|
"frame_length": self.LENGTH_SIG0_FRAME,
|
||||||
"origin": 6,
|
"origin": 6,
|
||||||
"gridsquare": 4
|
"gridsquare": 4,
|
||||||
|
"flag": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
def _load_ping_templates(self):
|
def _load_ping_templates(self):
|
||||||
|
@ -72,26 +77,6 @@ class DataFrameFactory:
|
||||||
"snr": 1,
|
"snr": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _load_fec_templates(self):
|
|
||||||
# fec wakeup frame
|
|
||||||
self.template_list[FR_TYPE.FEC_WAKEUP.value] = {
|
|
||||||
"frame_length": self.LENGTH_SIG0_FRAME,
|
|
||||||
"origin": 6,
|
|
||||||
"mode": 1,
|
|
||||||
"n_bursts": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# fec frame
|
|
||||||
self.template_list[FR_TYPE.FEC.value] = {
|
|
||||||
"frame_length": self.LENGTH_SIG0_FRAME,
|
|
||||||
"data": self.LENGTH_SIG0_FRAME - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# fec is writing frame
|
|
||||||
self.template_list[FR_TYPE.IS_WRITING.value] = {
|
|
||||||
"frame_length": self.LENGTH_SIG0_FRAME,
|
|
||||||
"origin": 6
|
|
||||||
}
|
|
||||||
|
|
||||||
def _load_arq_templates(self):
|
def _load_arq_templates(self):
|
||||||
|
|
||||||
|
@ -101,6 +86,7 @@ class DataFrameFactory:
|
||||||
"origin": 6,
|
"origin": 6,
|
||||||
"session_id": 1,
|
"session_id": 1,
|
||||||
"maximum_bandwidth": 2,
|
"maximum_bandwidth": 2,
|
||||||
|
"protocol_version" : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
self.template_list[FR_TYPE.ARQ_SESSION_OPEN_ACK.value] = {
|
self.template_list[FR_TYPE.ARQ_SESSION_OPEN_ACK.value] = {
|
||||||
|
@ -154,12 +140,12 @@ class DataFrameFactory:
|
||||||
|
|
||||||
# arq burst ack
|
# arq burst ack
|
||||||
self.template_list[FR_TYPE.ARQ_BURST_ACK.value] = {
|
self.template_list[FR_TYPE.ARQ_BURST_ACK.value] = {
|
||||||
"frame_length": self.LENGTH_SIG1_FRAME,
|
"frame_length": self.LENGTH_ACK_FRAME,
|
||||||
"session_id": 1,
|
"session_id": 1,
|
||||||
"offset":4,
|
#"offset":4,
|
||||||
"speed_level": 1,
|
"speed_level": 1,
|
||||||
"frames_per_burst": 1,
|
#"frames_per_burst": 1,
|
||||||
"snr": 1,
|
#"snr": 1,
|
||||||
"flag": 1,
|
"flag": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,9 +214,12 @@ class DataFrameFactory:
|
||||||
frame_length = frame_template["frame_length"]
|
frame_length = frame_template["frame_length"]
|
||||||
else:
|
else:
|
||||||
frame_length -= 2
|
frame_length -= 2
|
||||||
frame = bytearray(frame_length)
|
|
||||||
frame[:1] = bytes([frametype.value])
|
|
||||||
|
|
||||||
|
frame = bytearray(frame_length)
|
||||||
|
if frametype in [FR_TYPE.ARQ_BURST_ACK]:
|
||||||
|
buffer_position = 0
|
||||||
|
else:
|
||||||
|
frame[:1] = bytes([frametype.value])
|
||||||
buffer_position = 1
|
buffer_position = 1
|
||||||
for key, item_length in frame_template.items():
|
for key, item_length in frame_template.items():
|
||||||
if key == "frame_length":
|
if key == "frame_length":
|
||||||
|
@ -242,18 +231,23 @@ class DataFrameFactory:
|
||||||
raise OverflowError("Frame data overflow!")
|
raise OverflowError("Frame data overflow!")
|
||||||
frame[buffer_position: buffer_position + item_length] = content[key]
|
frame[buffer_position: buffer_position + item_length] = content[key]
|
||||||
buffer_position += item_length
|
buffer_position += item_length
|
||||||
|
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
def deconstruct(self, frame):
|
def deconstruct(self, frame, mode_name=None):
|
||||||
|
|
||||||
buffer_position = 1
|
buffer_position = 1
|
||||||
|
# Handle the case where the frame type is not recognized
|
||||||
|
#raise ValueError(f"Unknown frame type: {frametype}")
|
||||||
|
if mode_name in ["SIGNALLING_ACK"]:
|
||||||
|
frametype = FR_TYPE.ARQ_BURST_ACK.value
|
||||||
|
frame_template = self.template_list.get(frametype)
|
||||||
|
frame = bytes([frametype]) + frame
|
||||||
|
else:
|
||||||
# Extract frametype and get the corresponding template
|
# Extract frametype and get the corresponding template
|
||||||
frametype = int.from_bytes(frame[:1], "big")
|
frametype = int.from_bytes(frame[:1], "big")
|
||||||
frame_template = self.template_list.get(frametype)
|
frame_template = self.template_list.get(frametype)
|
||||||
|
|
||||||
if not frame_template:
|
|
||||||
# Handle the case where the frame type is not recognized
|
|
||||||
raise ValueError(f"Unknown frame type: {frametype}")
|
|
||||||
|
|
||||||
extracted_data = {"frame_type": FR_TYPE(frametype).name, "frame_type_int": frametype}
|
extracted_data = {"frame_type": FR_TYPE(frametype).name, "frame_type_int": frametype}
|
||||||
|
|
||||||
for key, item_length in frame_template.items():
|
for key, item_length in frame_template.items():
|
||||||
|
@ -262,6 +256,7 @@ class DataFrameFactory:
|
||||||
|
|
||||||
# data is always on the last payload slots
|
# data is always on the last payload slots
|
||||||
if item_length in ["dynamic"] and key in["data"]:
|
if item_length in ["dynamic"] and key in["data"]:
|
||||||
|
print(len(frame))
|
||||||
data = frame[buffer_position:-2]
|
data = frame[buffer_position:-2]
|
||||||
item_length = len(data)
|
item_length = len(data)
|
||||||
else:
|
else:
|
||||||
|
@ -279,7 +274,7 @@ class DataFrameFactory:
|
||||||
|
|
||||||
elif key in ["session_id", "speed_level",
|
elif key in ["session_id", "speed_level",
|
||||||
"frames_per_burst", "version",
|
"frames_per_burst", "version",
|
||||||
"offset", "total_length", "state", "type", "maximum_bandwidth"]:
|
"offset", "total_length", "state", "type", "maximum_bandwidth", "protocol_version"]:
|
||||||
extracted_data[key] = int.from_bytes(data, 'big')
|
extracted_data[key] = int.from_bytes(data, 'big')
|
||||||
|
|
||||||
elif key in ["snr"]:
|
elif key in ["snr"]:
|
||||||
|
@ -289,7 +284,6 @@ class DataFrameFactory:
|
||||||
|
|
||||||
data = int.from_bytes(data, "big")
|
data = int.from_bytes(data, "big")
|
||||||
extracted_data[key] = {}
|
extracted_data[key] = {}
|
||||||
|
|
||||||
# check for frametype for selecting the correspinding flag dictionary
|
# check for frametype for selecting the correspinding flag dictionary
|
||||||
if frametype in [FR_TYPE.ARQ_SESSION_OPEN_ACK.value, FR_TYPE.ARQ_SESSION_INFO_ACK.value, FR_TYPE.ARQ_BURST_ACK.value]:
|
if frametype in [FR_TYPE.ARQ_SESSION_OPEN_ACK.value, FR_TYPE.ARQ_SESSION_INFO_ACK.value, FR_TYPE.ARQ_BURST_ACK.value]:
|
||||||
flag_dict = self.ARQ_FLAGS
|
flag_dict = self.ARQ_FLAGS
|
||||||
|
@ -297,6 +291,15 @@ class DataFrameFactory:
|
||||||
# Update extracted_data with the status of each flag
|
# Update extracted_data with the status of each flag
|
||||||
# get_flag returns True or False based on the bit value at the flag's position
|
# get_flag returns True or False based on the bit value at the flag's position
|
||||||
extracted_data[key][flag] = helpers.get_flag(data, flag, flag_dict)
|
extracted_data[key][flag] = helpers.get_flag(data, flag, flag_dict)
|
||||||
|
|
||||||
|
if frametype in [FR_TYPE.BEACON.value]:
|
||||||
|
flag_dict = self.BEACON_FLAGS
|
||||||
|
for flag in flag_dict:
|
||||||
|
# Update extracted_data with the status of each flag
|
||||||
|
# get_flag returns True or False based on the bit value at the flag's position
|
||||||
|
extracted_data[key][flag] = helpers.get_flag(data, flag, flag_dict)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
extracted_data[key] = data
|
extracted_data[key] = data
|
||||||
|
|
||||||
|
@ -350,10 +353,16 @@ class DataFrameFactory:
|
||||||
}
|
}
|
||||||
return self.construct(FR_TYPE.QRV, payload)
|
return self.construct(FR_TYPE.QRV, payload)
|
||||||
|
|
||||||
def build_beacon(self):
|
def build_beacon(self, flag_away_from_key=False):
|
||||||
|
flag = 0b00000000
|
||||||
|
if flag_away_from_key:
|
||||||
|
flag = helpers.set_flag(flag, 'AWAY_FROM_KEY', True, self.BEACON_FLAGS)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"origin": helpers.callsign_to_bytes(self.myfullcall),
|
"origin": helpers.callsign_to_bytes(self.myfullcall),
|
||||||
"gridsquare": helpers.encode_grid(self.mygrid)
|
"gridsquare": helpers.encode_grid(self.mygrid),
|
||||||
|
"flag": flag.to_bytes(1, 'big'),
|
||||||
|
|
||||||
}
|
}
|
||||||
return self.construct(FR_TYPE.BEACON, payload)
|
return self.construct(FR_TYPE.BEACON, payload)
|
||||||
|
|
||||||
|
@ -388,12 +397,13 @@ class DataFrameFactory:
|
||||||
test_frame[:1] = bytes([FR_TYPE.TEST_FRAME.value])
|
test_frame[:1] = bytes([FR_TYPE.TEST_FRAME.value])
|
||||||
return test_frame
|
return test_frame
|
||||||
|
|
||||||
def build_arq_session_open(self, destination, session_id, maximum_bandwidth):
|
def build_arq_session_open(self, destination, session_id, maximum_bandwidth, protocol_version):
|
||||||
payload = {
|
payload = {
|
||||||
"destination_crc": helpers.get_crc_24(destination),
|
"destination_crc": helpers.get_crc_24(destination),
|
||||||
"origin": helpers.callsign_to_bytes(self.myfullcall),
|
"origin": helpers.callsign_to_bytes(self.myfullcall),
|
||||||
"session_id": session_id.to_bytes(1, 'big'),
|
"session_id": session_id.to_bytes(1, 'big'),
|
||||||
"maximum_bandwidth": maximum_bandwidth.to_bytes(2, 'big'),
|
"maximum_bandwidth": maximum_bandwidth.to_bytes(2, 'big'),
|
||||||
|
"protocol_version": protocol_version.to_bytes(1, 'big'),
|
||||||
}
|
}
|
||||||
return self.construct(FR_TYPE.ARQ_SESSION_OPEN, payload)
|
return self.construct(FR_TYPE.ARQ_SESSION_OPEN, payload)
|
||||||
|
|
||||||
|
@ -467,8 +477,7 @@ class DataFrameFactory:
|
||||||
FR_TYPE.ARQ_BURST_FRAME, payload, self.get_bytes_per_frame(freedv_mode)
|
FR_TYPE.ARQ_BURST_FRAME, payload, self.get_bytes_per_frame(freedv_mode)
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_arq_burst_ack(self, session_id: bytes, offset, speed_level: int,
|
def build_arq_burst_ack(self, session_id: bytes, speed_level: int, flag_final=False, flag_checksum=False, flag_abort=False):
|
||||||
frames_per_burst: int, snr: int, flag_final=False, flag_checksum=False, flag_abort=False):
|
|
||||||
flag = 0b00000000
|
flag = 0b00000000
|
||||||
if flag_final:
|
if flag_final:
|
||||||
flag = helpers.set_flag(flag, 'FINAL', True, self.ARQ_FLAGS)
|
flag = helpers.set_flag(flag, 'FINAL', True, self.ARQ_FLAGS)
|
||||||
|
@ -481,10 +490,7 @@ class DataFrameFactory:
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"session_id": session_id.to_bytes(1, 'big'),
|
"session_id": session_id.to_bytes(1, 'big'),
|
||||||
"offset": offset.to_bytes(4, 'big'),
|
|
||||||
"speed_level": speed_level.to_bytes(1, 'big'),
|
"speed_level": speed_level.to_bytes(1, 'big'),
|
||||||
"frames_per_burst": frames_per_burst.to_bytes(1, 'big'),
|
|
||||||
"snr": helpers.snr_to_bytes(snr),
|
|
||||||
"flag": flag.to_bytes(1, 'big'),
|
"flag": flag.to_bytes(1, 'big'),
|
||||||
}
|
}
|
||||||
return self.construct(FR_TYPE.ARQ_BURST_ACK, payload)
|
return self.construct(FR_TYPE.ARQ_BURST_ACK, payload)
|
||||||
|
|
|
@ -49,6 +49,10 @@ class Demodulator():
|
||||||
|
|
||||||
# enable decoding of signalling modes
|
# enable decoding of signalling modes
|
||||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||||
|
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = True
|
||||||
|
self.MODE_DICT[codec2.FREEDV_MODE.data_ofdm_2438.value]["decode"] = True
|
||||||
|
#self.MODE_DICT[codec2.FREEDV_MODE.qam16c2.value]["decode"] = True
|
||||||
|
|
||||||
|
|
||||||
tci_rx_callback_thread = threading.Thread(
|
tci_rx_callback_thread = threading.Thread(
|
||||||
target=self.tci_rx_callback,
|
target=self.tci_rx_callback,
|
||||||
|
@ -69,15 +73,13 @@ class Demodulator():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# create codec2 instance
|
# create codec2 instance
|
||||||
c2instance = ctypes.cast(
|
#c2instance = ctypes.cast(
|
||||||
codec2.api.freedv_open(mode), ctypes.c_void_p
|
c2instance = codec2.open_instance(mode)
|
||||||
)
|
|
||||||
|
|
||||||
# get bytes per frame
|
# get bytes per frame
|
||||||
bytes_per_frame = int(
|
bytes_per_frame = int(
|
||||||
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
||||||
)
|
)
|
||||||
|
|
||||||
# create byte out buffer
|
# create byte out buffer
|
||||||
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||||
|
|
||||||
|
@ -186,7 +188,7 @@ class Demodulator():
|
||||||
nin = codec2.api.freedv_nin(freedv)
|
nin = codec2.api.freedv_nin(freedv)
|
||||||
if nbytes == bytes_per_frame:
|
if nbytes == bytes_per_frame:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
|
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes, mode_name=mode_name
|
||||||
)
|
)
|
||||||
snr = self.calculate_snr(freedv)
|
snr = self.calculate_snr(freedv)
|
||||||
self.get_scatter(freedv)
|
self.get_scatter(freedv)
|
||||||
|
@ -197,7 +199,9 @@ class Demodulator():
|
||||||
'bytes_per_frame': bytes_per_frame,
|
'bytes_per_frame': bytes_per_frame,
|
||||||
'snr': snr,
|
'snr': snr,
|
||||||
'frequency_offset': self.get_frequency_offset(freedv),
|
'frequency_offset': self.get_frequency_offset(freedv),
|
||||||
|
'mode_name': mode_name
|
||||||
}
|
}
|
||||||
|
|
||||||
self.data_queue_received.put(item)
|
self.data_queue_received.put(item)
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,6 +348,7 @@ class Demodulator():
|
||||||
|
|
||||||
# signalling is always true
|
# signalling is always true
|
||||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||||
|
self.MODE_DICT[codec2.FREEDV_MODE.signalling_ack.value]["decode"] = True
|
||||||
|
|
||||||
# lowest speed level is alwys true
|
# lowest speed level is alwys true
|
||||||
self.MODE_DICT[codec2.FREEDV_MODE.datac4.value]["decode"] = True
|
self.MODE_DICT[codec2.FREEDV_MODE.datac4.value]["decode"] = True
|
||||||
|
|
|
@ -36,6 +36,7 @@ class explorer():
|
||||||
bandwidth = str(self.config['MODEM']['maximum_bandwidth'])
|
bandwidth = str(self.config['MODEM']['maximum_bandwidth'])
|
||||||
beacon = str(self.states.is_beacon_running)
|
beacon = str(self.states.is_beacon_running)
|
||||||
strength = str(self.states.s_meter_strength)
|
strength = str(self.states.s_meter_strength)
|
||||||
|
away_from_key = str(self.states.is_away_from_key)
|
||||||
|
|
||||||
# stop pushing if default callsign
|
# stop pushing if default callsign
|
||||||
if callsign in ['XX1XXX-6']:
|
if callsign in ['XX1XXX-6']:
|
||||||
|
@ -45,7 +46,7 @@ class explorer():
|
||||||
# log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
# log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'strength': strength, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []}
|
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'strength': strength, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": [], "away_from_key": away_from_key}
|
||||||
|
|
||||||
for i in self.states.heard_stations:
|
for i in self.states.heard_stations:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -46,9 +46,9 @@ class DISPATCHER():
|
||||||
FR_TYPE.PING_ACK.value: {"class": FrameHandler, "name": "PING ACK"},
|
FR_TYPE.PING_ACK.value: {"class": FrameHandler, "name": "PING ACK"},
|
||||||
FR_TYPE.PING.value: {"class": PingFrameHandler, "name": "PING"},
|
FR_TYPE.PING.value: {"class": PingFrameHandler, "name": "PING"},
|
||||||
FR_TYPE.QRV.value: {"class": FrameHandler, "name": "QRV"},
|
FR_TYPE.QRV.value: {"class": FrameHandler, "name": "QRV"},
|
||||||
FR_TYPE.IS_WRITING.value: {"class": FrameHandler, "name": "IS_WRITING"},
|
#FR_TYPE.IS_WRITING.value: {"class": FrameHandler, "name": "IS_WRITING"},
|
||||||
FR_TYPE.FEC.value: {"class": FrameHandler, "name": "FEC"},
|
#FR_TYPE.FEC.value: {"class": FrameHandler, "name": "FEC"},
|
||||||
FR_TYPE.FEC_WAKEUP.value: {"class": FrameHandler, "name": "FEC WAKEUP"},
|
#FR_TYPE.FEC_WAKEUP.value: {"class": FrameHandler, "name": "FEC WAKEUP"},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, config, event_manager, states, modem):
|
def __init__(self, config, event_manager, states, modem):
|
||||||
|
@ -79,17 +79,18 @@ class DISPATCHER():
|
||||||
"""Queue received data for processing"""
|
"""Queue received data for processing"""
|
||||||
while True:
|
while True:
|
||||||
data = self.data_queue_received.get()
|
data = self.data_queue_received.get()
|
||||||
self.new_process_data(
|
self.process_data(
|
||||||
data['payload'],
|
data['payload'],
|
||||||
data['freedv'],
|
data['freedv'],
|
||||||
data['bytes_per_frame'],
|
data['bytes_per_frame'],
|
||||||
data['snr'],
|
data['snr'],
|
||||||
data['frequency_offset'],
|
data['frequency_offset'],
|
||||||
|
data['mode_name'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def new_process_data(self, bytes_out, freedv, bytes_per_frame: int, snr, frequency_offset) -> None:
|
def process_data(self, bytes_out, freedv, bytes_per_frame: int, snr, frequency_offset, mode_name) -> None:
|
||||||
# get frame as dictionary
|
# get frame as dictionary
|
||||||
deconstructed_frame = self.frame_factory.deconstruct(bytes_out)
|
deconstructed_frame = self.frame_factory.deconstruct(bytes_out, mode_name=mode_name)
|
||||||
frametype = deconstructed_frame["frame_type_int"]
|
frametype = deconstructed_frame["frame_type_int"]
|
||||||
|
|
||||||
if frametype not in self.FRAME_HANDLER:
|
if frametype not in self.FRAME_HANDLER:
|
||||||
|
|
|
@ -101,6 +101,9 @@ class FrameHandler():
|
||||||
if "session_id" in frame:
|
if "session_id" in frame:
|
||||||
activity["session_id"] = frame["session_id"]
|
activity["session_id"] = frame["session_id"]
|
||||||
|
|
||||||
|
if "AWAY_FROM_KEY" in frame["flag"]:
|
||||||
|
activity["away_from_key"] = frame["flag"]["AWAY_FROM_KEY"]
|
||||||
|
|
||||||
self.states.add_activity(activity)
|
self.states.add_activity(activity)
|
||||||
|
|
||||||
def add_to_heard_stations(self):
|
def add_to_heard_stations(self):
|
||||||
|
@ -127,7 +130,8 @@ class FrameHandler():
|
||||||
self.states.radio_frequency,
|
self.states.radio_frequency,
|
||||||
self.states.heard_stations,
|
self.states.heard_stations,
|
||||||
distance_km=distance_km, # Pass the kilometer distance
|
distance_km=distance_km, # Pass the kilometer distance
|
||||||
distance_miles=distance_miles # Pass the miles distance
|
distance_miles=distance_miles, # Pass the miles distance
|
||||||
|
away_from_key=self.details['frame']["flag"]["AWAY_FROM_KEY"]
|
||||||
)
|
)
|
||||||
def make_event(self):
|
def make_event(self):
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ class CQFrameHandler(frame_handler.FrameHandler):
|
||||||
# return bool(self.config['MODEM']['respond_to_cq'] and not self.states.getARQ())
|
# return bool(self.config['MODEM']['respond_to_cq'] and not self.states.getARQ())
|
||||||
|
|
||||||
def follow_protocol(self):
|
def follow_protocol(self):
|
||||||
|
|
||||||
if self.states.getARQ():
|
if self.states.getARQ():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ import time
|
||||||
|
|
||||||
|
|
||||||
def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency, heard_stations_list, distance_km=None,
|
def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency, heard_stations_list, distance_km=None,
|
||||||
distance_miles=None):
|
distance_miles=None, away_from_key=False):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
dxcallsign (str): The callsign of the DX station.
|
dxcallsign (str): The callsign of the DX station.
|
||||||
|
@ -138,6 +138,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency,
|
||||||
heard_stations_list (list): List containing heard stations.
|
heard_stations_list (list): List containing heard stations.
|
||||||
distance_km (float): Distance to the DX station in kilometers.
|
distance_km (float): Distance to the DX station in kilometers.
|
||||||
distance_miles (float): Distance to the DX station in miles.
|
distance_miles (float): Distance to the DX station in miles.
|
||||||
|
away_from_key (bool): Away from key indicator
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Nothing. The function updates the heard_stations_list in-place.
|
Nothing. The function updates the heard_stations_list in-place.
|
||||||
|
@ -147,7 +148,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency,
|
||||||
|
|
||||||
# Initialize the new entry
|
# Initialize the new entry
|
||||||
new_entry = [
|
new_entry = [
|
||||||
dxcallsign, dxgrid, current_timestamp, datatype, snr, offset, frequency, distance_km, distance_miles
|
dxcallsign, dxgrid, current_timestamp, datatype, snr, offset, frequency, distance_km, distance_miles, away_from_key
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check if the buffer is empty or if the callsign is not already in the list
|
# Check if the buffer is empty or if the callsign is not already in the list
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -162,7 +162,7 @@ class RF:
|
||||||
callback=self.sd_output_audio_callback,
|
callback=self.sd_output_audio_callback,
|
||||||
device=out_dev_index,
|
device=out_dev_index,
|
||||||
samplerate=self.AUDIO_SAMPLE_RATE,
|
samplerate=self.AUDIO_SAMPLE_RATE,
|
||||||
blocksize=4800,
|
blocksize=1200,
|
||||||
)
|
)
|
||||||
self.sd_output_stream.start()
|
self.sd_output_stream.start()
|
||||||
|
|
||||||
|
@ -216,7 +216,6 @@ class RF:
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
self.demodulator.reset_data_sync()
|
self.demodulator.reset_data_sync()
|
||||||
|
|
||||||
# Wait for some other thread that might be transmitting
|
# Wait for some other thread that might be transmitting
|
||||||
self.states.waitForTransmission()
|
self.states.waitForTransmission()
|
||||||
self.states.setTransmitting(True)
|
self.states.setTransmitting(True)
|
||||||
|
@ -234,6 +233,8 @@ class RF:
|
||||||
else:
|
else:
|
||||||
txbuffer_out = x
|
txbuffer_out = x
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# transmit audio
|
# transmit audio
|
||||||
self.enqueue_audio_out(txbuffer_out)
|
self.enqueue_audio_out(txbuffer_out)
|
||||||
|
|
||||||
|
@ -245,11 +246,11 @@ class RF:
|
||||||
|
|
||||||
def enqueue_audio_out(self, audio_48k) -> None:
|
def enqueue_audio_out(self, audio_48k) -> None:
|
||||||
self.enqueuing_audio = True
|
self.enqueuing_audio = True
|
||||||
|
|
||||||
if not self.states.isTransmitting():
|
if not self.states.isTransmitting():
|
||||||
self.states.setTransmitting(True)
|
self.states.setTransmitting(True)
|
||||||
|
|
||||||
self.radio.set_ptt(True)
|
self.radio.set_ptt(True)
|
||||||
|
|
||||||
self.event_manager.send_ptt_change(True)
|
self.event_manager.send_ptt_change(True)
|
||||||
|
|
||||||
if self.radiocontrol in ["tci"]:
|
if self.radiocontrol in ["tci"]:
|
||||||
|
@ -258,7 +259,7 @@ class RF:
|
||||||
self.tci_module.wait_until_transmitted(audio_48k)
|
self.tci_module.wait_until_transmitted(audio_48k)
|
||||||
else:
|
else:
|
||||||
# slice audio data to needed blocklength
|
# slice audio data to needed blocklength
|
||||||
block_size = 4800
|
block_size = self.sd_output_stream.blocksize
|
||||||
pad_length = -len(audio_48k) % block_size
|
pad_length = -len(audio_48k) % block_size
|
||||||
padded_data = np.pad(audio_48k, (0, pad_length), mode='constant')
|
padded_data = np.pad(audio_48k, (0, pad_length), mode='constant')
|
||||||
sliced_audio_data = padded_data.reshape(-1, block_size)
|
sliced_audio_data = padded_data.reshape(-1, block_size)
|
||||||
|
@ -266,6 +267,7 @@ class RF:
|
||||||
for block in sliced_audio_data:
|
for block in sliced_audio_data:
|
||||||
self.audio_out_queue.put(block)
|
self.audio_out_queue.put(block)
|
||||||
|
|
||||||
|
|
||||||
self.enqueuing_audio = False
|
self.enqueuing_audio = False
|
||||||
self.states.transmitting_event.wait()
|
self.states.transmitting_event.wait()
|
||||||
|
|
||||||
|
@ -275,16 +277,18 @@ class RF:
|
||||||
return
|
return
|
||||||
|
|
||||||
def sd_output_audio_callback(self, outdata: np.ndarray, frames: int, time, status) -> None:
|
def sd_output_audio_callback(self, outdata: np.ndarray, frames: int, time, status) -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.audio_out_queue.empty():
|
if not self.audio_out_queue.empty() and not self.enqueuing_audio:
|
||||||
chunk = self.audio_out_queue.get_nowait()
|
chunk = self.audio_out_queue.get_nowait()
|
||||||
audio.calculate_fft(chunk, self.fft_queue, self.states)
|
audio_8k = self.resampler.resample48_to_8(chunk)
|
||||||
|
audio.calculate_fft(audio_8k, self.fft_queue, self.states)
|
||||||
outdata[:] = chunk.reshape(outdata.shape)
|
outdata[:] = chunk.reshape(outdata.shape)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# reset transmitting state only, if we are not actively processing audio
|
# reset transmitting state only, if we are not actively processing audio
|
||||||
# for avoiding a ptt toggle state bug
|
# for avoiding a ptt toggle state bug
|
||||||
if not self.enqueuing_audio:
|
if self.audio_out_queue.empty() and not self.enqueuing_audio:
|
||||||
self.states.setTransmitting(False)
|
self.states.setTransmitting(False)
|
||||||
# Fill with zeros if the queue is empty
|
# Fill with zeros if the queue is empty
|
||||||
outdata.fill(0)
|
outdata.fill(0)
|
||||||
|
@ -325,4 +329,3 @@ class RF:
|
||||||
audiobuffer.push(audio_8k_level_adjusted)
|
audiobuffer.push(audio_8k_level_adjusted)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.warning("[AUDIO EXCEPTION]", status=status, time=time, frames=frames, e=e)
|
self.log.warning("[AUDIO EXCEPTION]", status=status, time=time, frames=frames, e=e)
|
||||||
|
|
||||||
|
|
|
@ -22,16 +22,16 @@ class FRAME_TYPE(Enum):
|
||||||
P2P_CONNECTION_PAYLOAD_ACK = 35
|
P2P_CONNECTION_PAYLOAD_ACK = 35
|
||||||
P2P_CONNECTION_DISCONNECT = 36
|
P2P_CONNECTION_DISCONNECT = 36
|
||||||
P2P_CONNECTION_DISCONNECT_ACK = 37
|
P2P_CONNECTION_DISCONNECT_ACK = 37
|
||||||
MESH_BROADCAST = 100
|
#MESH_BROADCAST = 100
|
||||||
MESH_SIGNALLING_PING = 101
|
#MESH_SIGNALLING_PING = 101
|
||||||
MESH_SIGNALLING_PING_ACK = 102
|
#MESH_SIGNALLING_PING_ACK = 102
|
||||||
CQ = 200
|
CQ = 200
|
||||||
QRV = 201
|
QRV = 201
|
||||||
PING = 210
|
PING = 210
|
||||||
PING_ACK = 211
|
PING_ACK = 211
|
||||||
IS_WRITING = 215
|
#IS_WRITING = 215
|
||||||
BEACON = 250
|
BEACON = 250
|
||||||
FEC = 251
|
#FEC = 251
|
||||||
FEC_WAKEUP = 252
|
#FEC_WAKEUP = 252
|
||||||
IDENT = 254
|
IDENT = 254
|
||||||
TEST_FRAME = 255
|
TEST_FRAME = 255
|
||||||
|
|
|
@ -23,6 +23,11 @@ class Modulator:
|
||||||
self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.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_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value)
|
||||||
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
|
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
|
||||||
|
self.freedv_datac14_tx = codec2.open_instance(codec2.FREEDV_MODE.datac14.value)
|
||||||
|
self.data_ofdm_500_tx = codec2.open_instance(codec2.FREEDV_MODE.data_ofdm_500.value)
|
||||||
|
self.data_ofdm_2438_tx = codec2.open_instance(codec2.FREEDV_MODE.data_ofdm_2438.value)
|
||||||
|
self.freedv_qam16c2_tx = codec2.open_instance(codec2.FREEDV_MODE.qam16c2.value)
|
||||||
|
#self.data_qam_2438_tx = codec2.open_instance(codec2.FREEDV_MODE.data_qam_2438.value)
|
||||||
|
|
||||||
def transmit_add_preamble(self, buffer, freedv):
|
def transmit_add_preamble(self, buffer, freedv):
|
||||||
# Init buffer for preample
|
# Init buffer for preample
|
||||||
|
@ -82,7 +87,6 @@ class Modulator:
|
||||||
crc = crc.value.to_bytes(2, byteorder="big")
|
crc = crc.value.to_bytes(2, byteorder="big")
|
||||||
# Append CRC to data buffer
|
# Append CRC to data buffer
|
||||||
buffer += crc
|
buffer += crc
|
||||||
|
|
||||||
assert (bytes_per_frame == len(buffer))
|
assert (bytes_per_frame == len(buffer))
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
# modulate DATA and save it into mod_out pointer
|
# modulate DATA and save it into mod_out pointer
|
||||||
|
@ -107,19 +111,25 @@ class Modulator:
|
||||||
|
|
||||||
# get freedv instance by mode
|
# get freedv instance by mode
|
||||||
mode_transition = {
|
mode_transition = {
|
||||||
|
codec2.FREEDV_MODE.signalling_ack: self.freedv_datac14_tx,
|
||||||
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
||||||
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
||||||
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
||||||
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
||||||
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
||||||
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
||||||
|
codec2.FREEDV_MODE.datac14: self.freedv_datac14_tx,
|
||||||
|
codec2.FREEDV_MODE.data_ofdm_500: self.data_ofdm_500_tx,
|
||||||
|
codec2.FREEDV_MODE.data_ofdm_2438: self.data_ofdm_2438_tx,
|
||||||
|
codec2.FREEDV_MODE.qam16c2: self.freedv_qam16c2_tx,
|
||||||
|
#codec2.FREEDV_MODE.data_qam_2438: self.freedv_data_qam_2438_tx,
|
||||||
}
|
}
|
||||||
if mode in mode_transition:
|
if mode in mode_transition:
|
||||||
freedv = mode_transition[mode]
|
freedv = mode_transition[mode]
|
||||||
else:
|
else:
|
||||||
print("wrong mode.................")
|
print("wrong mode.................")
|
||||||
print(mode)
|
print(mode)
|
||||||
return False
|
#return False
|
||||||
|
|
||||||
|
|
||||||
# Open codec2 instance
|
# Open codec2 instance
|
||||||
|
|
|
@ -33,7 +33,7 @@ from schedule_manager import ScheduleManager
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app, resources={r"/*": {"origins": "*"}})
|
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||||
sock = Sock(app)
|
sock = Sock(app)
|
||||||
MODEM_VERSION = "0.14.5-alpha"
|
MODEM_VERSION = "0.15.2-alpha"
|
||||||
|
|
||||||
# set config file to use
|
# set config file to use
|
||||||
def set_config():
|
def set_config():
|
||||||
|
@ -146,14 +146,15 @@ def post_cqcqcq():
|
||||||
def post_beacon():
|
def post_beacon():
|
||||||
if request.method not in ['POST']:
|
if request.method not in ['POST']:
|
||||||
return api_response({"info": "endpoint for controlling BEACON STATE via POST"})
|
return api_response({"info": "endpoint for controlling BEACON STATE via POST"})
|
||||||
|
if not isinstance(request.json['enabled'], bool) or not isinstance(request.json['away_from_key'], bool):
|
||||||
if not isinstance(request.json['enabled'], bool):
|
|
||||||
api_abort(f"Incorrect value for 'enabled'. Shoud be bool.")
|
api_abort(f"Incorrect value for 'enabled'. Shoud be bool.")
|
||||||
if not app.state_manager.is_modem_running:
|
if not app.state_manager.is_modem_running:
|
||||||
api_abort('Modem not running', 503)
|
api_abort('Modem not running', 503)
|
||||||
|
|
||||||
if not app.state_manager.is_beacon_running:
|
if not app.state_manager.is_beacon_running:
|
||||||
app.state_manager.set('is_beacon_running', request.json['enabled'])
|
app.state_manager.set('is_beacon_running', request.json['enabled'])
|
||||||
|
app.state_manager.set('is_away_from_key', request.json['away_from_key'])
|
||||||
|
|
||||||
if not app.state_manager.getARQ():
|
if not app.state_manager.getARQ():
|
||||||
enqueue_tx_command(command_beacon.BeaconCommand, request.json)
|
enqueue_tx_command(command_beacon.BeaconCommand, request.json)
|
||||||
else:
|
else:
|
||||||
|
@ -326,18 +327,21 @@ def sock_states(sock):
|
||||||
|
|
||||||
@atexit.register
|
@atexit.register
|
||||||
def stop_server():
|
def stop_server():
|
||||||
|
print("------------------------------------------")
|
||||||
try:
|
try:
|
||||||
app.service_manager.modem_service.put("stop")
|
app.service_manager.modem_service.put("stop")
|
||||||
|
if app.socket_interface_manager:
|
||||||
app.socket_interface_manager.stop_servers()
|
app.socket_interface_manager.stop_servers()
|
||||||
|
|
||||||
if app.service_manager.modem:
|
if app.service_manager.modem:
|
||||||
app.service_manager.modem.sd_input_stream.stop
|
app.service_manager.modem.sd_input_stream.stop
|
||||||
audio.sd._terminate()
|
audio.sd._terminate()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
print("Error stopping modem")
|
print("Error stopping modem")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
print("------------------------------------------")
|
|
||||||
print('Server shutdown...')
|
print('Server shutdown...')
|
||||||
|
print("------------------------------------------")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
|
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class SM:
|
||||||
elif cmd in ['stop'] and self.modem:
|
elif cmd in ['stop'] and self.modem:
|
||||||
self.stop_modem()
|
self.stop_modem()
|
||||||
self.stop_radio_manager()
|
self.stop_radio_manager()
|
||||||
if self.config['SOCKET_INTERFACE']['enable']:
|
if self.config['SOCKET_INTERFACE']['enable'] and self.socket_interface_manager:
|
||||||
self.socket_interface_manager.stop_servers()
|
self.socket_interface_manager.stop_servers()
|
||||||
# we need to wait a bit for avoiding a portaudio crash
|
# we need to wait a bit for avoiding a portaudio crash
|
||||||
threading.Event().wait(0.5)
|
threading.Event().wait(0.5)
|
||||||
|
@ -48,7 +48,8 @@ class SM:
|
||||||
elif cmd in ['restart']:
|
elif cmd in ['restart']:
|
||||||
self.stop_modem()
|
self.stop_modem()
|
||||||
self.stop_radio_manager()
|
self.stop_radio_manager()
|
||||||
if self.config['SOCKET_INTERFACE']['enable']:
|
if self.config['SOCKET_INTERFACE']['enable'] and self.socket_interface_manager:
|
||||||
|
|
||||||
self.socket_interface_manager.stop_servers()
|
self.socket_interface_manager.stop_servers()
|
||||||
|
|
||||||
# we need to wait a bit for avoiding a portaudio crash
|
# we need to wait a bit for avoiding a portaudio crash
|
||||||
|
|
|
@ -23,6 +23,7 @@ class StateManager:
|
||||||
self.setARQ(False)
|
self.setARQ(False)
|
||||||
|
|
||||||
self.is_beacon_running = False
|
self.is_beacon_running = False
|
||||||
|
self.is_away_from_key = False
|
||||||
|
|
||||||
# If true, any wait() call is blocking
|
# If true, any wait() call is blocking
|
||||||
self.transmitting_event = threading.Event()
|
self.transmitting_event = threading.Event()
|
||||||
|
@ -84,6 +85,7 @@ class StateManager:
|
||||||
"type": msgtype,
|
"type": msgtype,
|
||||||
"is_modem_running": self.is_modem_running,
|
"is_modem_running": self.is_modem_running,
|
||||||
"is_beacon_running": self.is_beacon_running,
|
"is_beacon_running": self.is_beacon_running,
|
||||||
|
"is_away_from_key": self.is_away_from_key,
|
||||||
"radio_status": self.radio_status,
|
"radio_status": self.radio_status,
|
||||||
"radio_frequency": self.radio_frequency,
|
"radio_frequency": self.radio_frequency,
|
||||||
"radio_mode": self.radio_mode,
|
"radio_mode": self.radio_mode,
|
||||||
|
@ -184,7 +186,6 @@ class StateManager:
|
||||||
# if frequency not provided, add it here
|
# if frequency not provided, add it here
|
||||||
if 'frequency' not in activity_data:
|
if 'frequency' not in activity_data:
|
||||||
activity_data['frequency'] = self.radio_frequency
|
activity_data['frequency'] = self.radio_frequency
|
||||||
|
|
||||||
self.activities_list[activity_id] = activity_data
|
self.activities_list[activity_id] = activity_data
|
||||||
self.sendStateUpdate()
|
self.sendStateUpdate()
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,10 @@ class TestModem:
|
||||||
samples += codec2.api.freedv_get_n_tx_modem_samples(c2instance)
|
samples += codec2.api.freedv_get_n_tx_modem_samples(c2instance)
|
||||||
samples += codec2.api.freedv_get_n_tx_postamble_modem_samples(c2instance)
|
samples += codec2.api.freedv_get_n_tx_postamble_modem_samples(c2instance)
|
||||||
time = samples / 8000
|
time = samples / 8000
|
||||||
|
#print(mode)
|
||||||
|
#if mode == codec2.FREEDV_MODE.signalling:
|
||||||
|
# time = 0.69
|
||||||
|
#print(time)
|
||||||
return time
|
return time
|
||||||
|
|
||||||
def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray) -> bool:
|
def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray) -> bool:
|
||||||
|
@ -82,7 +86,7 @@ class TestARQSession(unittest.TestCase):
|
||||||
cls.irs_modem)
|
cls.irs_modem)
|
||||||
|
|
||||||
# Frame loss probability in %
|
# Frame loss probability in %
|
||||||
cls.loss_probability = 30
|
cls.loss_probability = 0
|
||||||
|
|
||||||
cls.channels_running = True
|
cls.channels_running = True
|
||||||
|
|
||||||
|
@ -91,12 +95,18 @@ class TestARQSession(unittest.TestCase):
|
||||||
# Transfer data between both parties
|
# Transfer data between both parties
|
||||||
try:
|
try:
|
||||||
transmission = modem_transmit_queue.get(timeout=1)
|
transmission = modem_transmit_queue.get(timeout=1)
|
||||||
|
transmission["bytes"] += bytes(2) # simulate 2 bytes crc checksum
|
||||||
if random.randint(0, 100) < self.loss_probability:
|
if random.randint(0, 100) < self.loss_probability:
|
||||||
self.logger.info(f"[{threading.current_thread().name}] Frame lost...")
|
self.logger.info(f"[{threading.current_thread().name}] Frame lost...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
frame_bytes = transmission['bytes']
|
frame_bytes = transmission['bytes']
|
||||||
frame_dispatcher.new_process_data(frame_bytes, None, len(frame_bytes), 0, 0)
|
|
||||||
|
if len(frame_bytes) == 5:
|
||||||
|
mode_name = "SIGNALLING_ACK"
|
||||||
|
else:
|
||||||
|
mode_name = None
|
||||||
|
frame_dispatcher.process_data(frame_bytes, None, len(frame_bytes), 5, 0, mode_name=mode_name)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
continue
|
continue
|
||||||
self.logger.info(f"[{threading.current_thread().name}] Channel closed.")
|
self.logger.info(f"[{threading.current_thread().name}] Channel closed.")
|
||||||
|
@ -129,7 +139,7 @@ class TestARQSession(unittest.TestCase):
|
||||||
self.waitForSession(self.irs_event_queue, False)
|
self.waitForSession(self.irs_event_queue, False)
|
||||||
self.channels_running = False
|
self.channels_running = False
|
||||||
|
|
||||||
def testARQSessionSmallPayload(self):
|
def DisabledtestARQSessionSmallPayload(self):
|
||||||
# set Packet Error Rate (PER) / frame loss probability
|
# set Packet Error Rate (PER) / frame loss probability
|
||||||
self.loss_probability = 30
|
self.loss_probability = 30
|
||||||
|
|
||||||
|
@ -160,7 +170,7 @@ class TestARQSession(unittest.TestCase):
|
||||||
self.waitAndCloseChannels()
|
self.waitAndCloseChannels()
|
||||||
del cmd
|
del cmd
|
||||||
|
|
||||||
def testARQSessionAbortTransmissionISS(self):
|
def DisabledtestARQSessionAbortTransmissionISS(self):
|
||||||
# set Packet Error Rate (PER) / frame loss probability
|
# set Packet Error Rate (PER) / frame loss probability
|
||||||
self.loss_probability = 0
|
self.loss_probability = 0
|
||||||
|
|
||||||
|
@ -172,14 +182,14 @@ class TestARQSession(unittest.TestCase):
|
||||||
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
||||||
cmd.run(self.iss_event_queue, self.iss_modem)
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
||||||
|
|
||||||
threading.Event().wait(np.random.randint(1,10))
|
threading.Event().wait(np.random.randint(10,10))
|
||||||
for id in self.iss_state_manager.arq_iss_sessions:
|
for id in self.iss_state_manager.arq_iss_sessions:
|
||||||
self.iss_state_manager.arq_iss_sessions[id].abort_transmission()
|
self.iss_state_manager.arq_iss_sessions[id].abort_transmission()
|
||||||
|
|
||||||
self.waitAndCloseChannels()
|
self.waitAndCloseChannels()
|
||||||
del cmd
|
del cmd
|
||||||
|
|
||||||
def testARQSessionAbortTransmissionIRS(self):
|
def DisabledtestARQSessionAbortTransmissionIRS(self):
|
||||||
# set Packet Error Rate (PER) / frame loss probability
|
# set Packet Error Rate (PER) / frame loss probability
|
||||||
self.loss_probability = 0
|
self.loss_probability = 0
|
||||||
|
|
||||||
|
@ -198,7 +208,7 @@ class TestARQSession(unittest.TestCase):
|
||||||
self.waitAndCloseChannels()
|
self.waitAndCloseChannels()
|
||||||
del cmd
|
del cmd
|
||||||
|
|
||||||
def testSessionCleanupISS(self):
|
def DisabledtestSessionCleanupISS(self):
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'dxcall': "AA1AAA-1",
|
'dxcall': "AA1AAA-1",
|
||||||
|
@ -217,7 +227,7 @@ class TestARQSession(unittest.TestCase):
|
||||||
break
|
break
|
||||||
del cmd
|
del cmd
|
||||||
|
|
||||||
def testSessionCleanupIRS(self):
|
def DisabledtestSessionCleanupIRS(self):
|
||||||
session = arq_session_irs.ARQSessionIRS(self.config,
|
session = arq_session_irs.ARQSessionIRS(self.config,
|
||||||
self.irs_modem,
|
self.irs_modem,
|
||||||
'AA1AAA-1',
|
'AA1AAA-1',
|
||||||
|
|
96
tools/custom_mode_tests/create_custom_ofdm_mod.py
Normal file
96
tools/custom_mode_tests/create_custom_ofdm_mod.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
FreeDATA % python3.11 tools/custom_mode_tests/create_custom_ofdm_mod.py | ./modem/lib/codec2/build_osx/src/freedv_data_raw_rx --vv --framesperburst 1 DATAC1 - /dev/null
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
sys.path.append('modem')
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
modem_path = '/../../modem'
|
||||||
|
if modem_path not in sys.path:
|
||||||
|
sys.path.append(modem_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#import modem.codec2 as codec2
|
||||||
|
from codec2 import *
|
||||||
|
import threading
|
||||||
|
import modulator as modulator
|
||||||
|
import demodulator as demodulator
|
||||||
|
import config as config
|
||||||
|
|
||||||
|
|
||||||
|
def demod(txbuffer):
|
||||||
|
c2instance = open_instance(FREEDV_MODE.datac3.value)
|
||||||
|
|
||||||
|
# get bytes per frame
|
||||||
|
bytes_per_frame = int(
|
||||||
|
api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
||||||
|
)
|
||||||
|
# create byte out buffer
|
||||||
|
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||||
|
|
||||||
|
# set initial frames per burst
|
||||||
|
api.freedv_set_frames_per_burst(c2instance, 1)
|
||||||
|
|
||||||
|
# init audio buffer
|
||||||
|
audiobuffer = audio_buffer(len(txbuffer))
|
||||||
|
|
||||||
|
|
||||||
|
# get initial nin
|
||||||
|
nin = api.freedv_nin(c2instance)
|
||||||
|
audiobuffer.push(txbuffer)
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
while audiobuffer.nbuffer >= nin:
|
||||||
|
# demodulate audio
|
||||||
|
nbytes = api.freedv_rawdatarx(
|
||||||
|
freedv, bytes_out, audiobuffer.buffer.ctypes
|
||||||
|
)
|
||||||
|
# get current modem states and write to list
|
||||||
|
# 1 trial
|
||||||
|
# 2 sync
|
||||||
|
# 3 trial sync
|
||||||
|
# 6 decoded
|
||||||
|
# 10 error decoding == NACK
|
||||||
|
rx_status = api.freedv_get_rx_status(freedv)
|
||||||
|
print(rx_status)
|
||||||
|
|
||||||
|
# decrement codec traffic counter for making state smoother
|
||||||
|
|
||||||
|
audiobuffer.pop(nin)
|
||||||
|
nin = api.freedv_nin(freedv)
|
||||||
|
if nbytes == bytes_per_frame:
|
||||||
|
print("DECODED!!!!")
|
||||||
|
print("ENDED")
|
||||||
|
print(nin)
|
||||||
|
print(audiobuffer.nbuffer)
|
||||||
|
|
||||||
|
config = config.CONFIG('config.ini')
|
||||||
|
modulator = modulator.Modulator(config.read())
|
||||||
|
#freedv = open_instance(FREEDV_MODE.data_ofdm_2438.value)
|
||||||
|
#freedv = open_instance(FREEDV_MODE.datac14.value)
|
||||||
|
#freedv = open_instance(FREEDV_MODE.datac1.value)
|
||||||
|
freedv = open_instance(FREEDV_MODE.datac3.value)
|
||||||
|
#freedv = open_instance(FREEDV_MODE.data_ofdm_500.value)
|
||||||
|
#freedv = open_instance(FREEDV_MODE.qam16c2.value)
|
||||||
|
|
||||||
|
|
||||||
|
frames = 2
|
||||||
|
txbuffer = bytearray()
|
||||||
|
|
||||||
|
for frame in range(0,frames):
|
||||||
|
#txbuffer = modulator.transmit_add_silence(txbuffer, 1000)
|
||||||
|
txbuffer = modulator.transmit_add_preamble(txbuffer, freedv)
|
||||||
|
txbuffer = modulator.transmit_create_frame(txbuffer, freedv, b'123')
|
||||||
|
txbuffer = modulator.transmit_add_postamble(txbuffer, freedv)
|
||||||
|
txbuffer = modulator.transmit_add_silence(txbuffer, 1000)
|
||||||
|
|
||||||
|
#sys.stdout.buffer.flush()
|
||||||
|
#sys.stdout.buffer.write(txbuffer)
|
||||||
|
#sys.stdout.buffer.flush()
|
||||||
|
demod(txbuffer)
|
||||||
|
|
Loading…
Reference in a new issue