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
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
export PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.11/lib/:$PYTHONPATH
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "FreeDATA",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"version": "0.14.5-alpha",
|
||||
"version": "0.15.2-alpha",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
|
|
@ -105,6 +105,8 @@ Spectrum.prototype.drawSpectrum = function () {
|
|||
var linePositionHigh = 178.4; //150 + bandwidth/20
|
||||
var linePositionLow2 = 65; //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.moveTo(linePositionLow, 0);
|
||||
this.ctx_wf.lineTo(linePositionLow, height);
|
||||
|
@ -114,6 +116,10 @@ Spectrum.prototype.drawSpectrum = function () {
|
|||
this.ctx_wf.lineTo(linePositionLow2, height);
|
||||
this.ctx_wf.moveTo(linePositionHigh2, 0);
|
||||
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.strokeStyle = "#C3C3C3";
|
||||
this.ctx_wf.stroke();
|
||||
|
@ -454,7 +460,7 @@ export function Spectrum(id, options) {
|
|||
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
|
||||
this.spanHz = options && options.spanHz ? options.spanHz : 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 =
|
||||
options && options.spectrumPercent ? options.spectrumPercent : 0;
|
||||
this.spectrumPercentStep =
|
||||
|
|
|
@ -32,8 +32,7 @@ import grid_freq from "./grid/grid_frequency.vue";
|
|||
import grid_beacon from "./grid/grid_beacon.vue";
|
||||
import grid_mycall_small from "./grid/grid_mycall small.vue";
|
||||
import grid_scatter from "./grid/grid_scatter.vue";
|
||||
import { stateDispatcher } from "../js/eventHandler";
|
||||
import { Scatter } from "vue-chartjs";
|
||||
import grid_stats_chart from "./grid/grid_stats_chart.vue";
|
||||
|
||||
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
|
||||
|
@ -63,7 +62,8 @@ class gridWidget {
|
|||
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 = [
|
||||
new gridWidget(
|
||||
grid_activities,
|
||||
|
@ -247,8 +247,16 @@ new gridWidget(
|
|||
"Stats",
|
||||
19,
|
||||
),
|
||||
|
||||
//New new widget ID should be 20
|
||||
new gridWidget(
|
||||
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() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
setModemBeacon(false, state.away_from_key);
|
||||
} 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("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
|
@ -39,7 +50,7 @@ window.addEventListener(
|
|||
<div class="card-body overflow-auto p-0">
|
||||
<div class="container text-center">
|
||||
<div class="row mb-2 mt-2">
|
||||
<div class="col-sm-8">
|
||||
<div class="col">
|
||||
<div class="input-group w-100">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
|
@ -65,25 +76,10 @@ window.addEventListener(
|
|||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
<strong>Ping</strong>
|
||||
<strong>PING Station</strong>
|
||||
</button>
|
||||
</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 class="row">
|
||||
|
@ -99,6 +95,41 @@ window.addEventListener(
|
|||
</button>
|
||||
</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>
|
||||
|
|
|
@ -34,9 +34,10 @@ function getMaidenheadDistance(dxGrid) {
|
|||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
function pushToPing(origin) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("stationSelected", { bubbles: true, detail: origin }),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -61,11 +62,16 @@ function pushToPing(origin)
|
|||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
<th scope="col" id="thSnr">AFK?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="gridHeardStations">
|
||||
<!--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>
|
||||
{{ getDateTime(item.timestamp) }}
|
||||
</td>
|
||||
|
@ -83,6 +89,9 @@ function pushToPing(origin)
|
|||
<td>
|
||||
{{ item.snr }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.away_from_key }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<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 } from "vue";
|
||||
import { ref, computed, onMounted, nextTick, toRaw } from "vue";
|
||||
import { initWaterfall, setColormap } from "../../js/waterfallHandler.js";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
|
@ -89,7 +89,7 @@ const transmissionSpeedChartData = computed(() => ({
|
|||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
data: state.arq_speed_list_snr.value,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
|
@ -106,7 +106,7 @@ const transmissionSpeedChartData = computed(() => ({
|
|||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
data: state.arq_speed_list_bpm.value,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useStateStore } from "../../store/stateStore.js";
|
|||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<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
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
|
@ -24,7 +24,7 @@ const state = useStateStore(pinia);
|
|||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</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
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useStateStore } from "../../store/stateStore.js";
|
|||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<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
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
|
@ -24,7 +24,7 @@ const state = useStateStore(pinia);
|
|||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</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
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
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);
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<div class="fill h-100">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
data-bs-trigger="hover"
|
||||
|
|
|
@ -175,6 +175,7 @@ const audioStore = useAudioStore();
|
|||
<option value="250">250 Hz</option>
|
||||
<option value="563">563 Hz</option>
|
||||
<option value="1700">1700 Hz</option>
|
||||
<option value="2438">2438 Hz</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -92,8 +92,11 @@ export async function getSerialDevices() {
|
|||
return await apiGet("/devices/serial");
|
||||
}
|
||||
|
||||
export async function setModemBeacon(enabled = false) {
|
||||
return await apiPost("/modem/beacon", { enabled: enabled });
|
||||
export async function setModemBeacon(enabled = false, away_from_key = false) {
|
||||
return await apiPost("/modem/beacon", {
|
||||
enabled: enabled,
|
||||
away_from_key: away_from_key,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendModemCQ() {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
setStateFailed,
|
||||
} from "./chatHandler";
|
||||
*/
|
||||
import { toRaw } from "vue";
|
||||
import { displayToast } from "./popupHandler";
|
||||
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
|
||||
import { processFreedataMessages } from "./messagesHandler.ts";
|
||||
|
@ -29,8 +30,11 @@ import {
|
|||
getRemote,
|
||||
} from "../store/settingsStore.js";
|
||||
|
||||
export function loadAllData() {
|
||||
getModemState();
|
||||
export async function loadAllData() {
|
||||
// TODO: Make this working
|
||||
let stateData = await getModemState();
|
||||
console.log(stateData);
|
||||
|
||||
getRemote();
|
||||
getOverallHealth();
|
||||
audioStore.loadAudioDevices();
|
||||
|
@ -66,7 +70,10 @@ export function stateDispatcher(data) {
|
|||
);
|
||||
|
||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||
|
||||
stateStore.beacon_state = data["is_beacon_running"];
|
||||
stateStore.is_away_from_key = data["is_away_from_key"];
|
||||
|
||||
stateStore.radio_status = data["radio_status"];
|
||||
stateStore.frequency = data["radio_frequency"];
|
||||
stateStore.mode = data["radio_mode"];
|
||||
|
@ -178,12 +185,16 @@ export function eventDispatcher(data) {
|
|||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-outbound"].received_bytes;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-outbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-outbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-outbound"].statistics.snr_histogram;
|
||||
stateStore.arq_speed_list_timestamp.value = toRaw(
|
||||
data["arq-transfer-outbound"].statistics.time_histogram,
|
||||
);
|
||||
stateStore.arq_speed_list_bpm.value = toRaw(
|
||||
data["arq-transfer-outbound"].statistics.bpm_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;
|
||||
|
||||
case "ABORTING":
|
||||
|
@ -226,13 +237,12 @@ export function eventDispatcher(data) {
|
|||
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
|
||||
stateStore.arq_transmission_percent = 0;
|
||||
stateStore.arq_total_bytes = 0;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-inbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-inbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-inbound"].statistics.snr_histogram;
|
||||
|
||||
//stateStore.arq_speed_list_timestamp =
|
||||
// [];
|
||||
//stateStore.arq_speed_list_bpm =
|
||||
// [];
|
||||
//stateStore.arq_speed_list_snr =
|
||||
// [];
|
||||
return;
|
||||
|
||||
case "OPEN_ACK_SENT":
|
||||
|
@ -266,6 +276,19 @@ export function eventDispatcher(data) {
|
|||
100;
|
||||
stateStore.arq_total_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;
|
||||
|
||||
case "ENDED":
|
||||
|
|
|
@ -38,6 +38,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
var arq_session_state = ref("");
|
||||
var arq_state = ref("");
|
||||
var beacon_state = ref(false);
|
||||
var away_from_key = ref(false);
|
||||
|
||||
var audio_recording = ref(false);
|
||||
|
||||
|
@ -115,6 +116,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
activities,
|
||||
heard_stations,
|
||||
beacon_state,
|
||||
away_from_key,
|
||||
rigctld_started,
|
||||
rigctld_process,
|
||||
python_version,
|
||||
|
|
5
modem/.gitignore
vendored
5
modem/.gitignore
vendored
|
@ -129,4 +129,7 @@ dmypy.json
|
|||
.pyre/
|
||||
|
||||
# FreeDATA config
|
||||
config.ini
|
||||
config.ini
|
||||
|
||||
#FreeData DB
|
||||
freedata-messages.db
|
|
@ -3,6 +3,7 @@
|
|||
import structlog
|
||||
import lzma
|
||||
import gzip
|
||||
import zlib
|
||||
from message_p2p import message_received, message_failed, message_transmitted
|
||||
from enum import Enum
|
||||
|
||||
|
@ -10,7 +11,7 @@ class ARQ_SESSION_TYPES(Enum):
|
|||
raw = 0
|
||||
raw_lzma = 10
|
||||
raw_gzip = 11
|
||||
p2pmsg_lzma = 20
|
||||
p2pmsg_zlib = 20
|
||||
p2p_connection = 30
|
||||
|
||||
class ARQDataTypeHandler:
|
||||
|
@ -38,11 +39,11 @@ class ARQDataTypeHandler:
|
|||
'failed': self.failed_raw_gzip,
|
||||
'transmitted': self.transmitted_raw_gzip,
|
||||
},
|
||||
ARQ_SESSION_TYPES.p2pmsg_lzma: {
|
||||
'prepare': self.prepare_p2pmsg_lzma,
|
||||
'handle': self.handle_p2pmsg_lzma,
|
||||
'failed' : self.failed_p2pmsg_lzma,
|
||||
'transmitted': self.transmitted_p2pmsg_lzma,
|
||||
ARQ_SESSION_TYPES.p2pmsg_zlib: {
|
||||
'prepare': self.prepare_p2pmsg_zlib,
|
||||
'handle': self.handle_p2pmsg_zlib,
|
||||
'failed' : self.failed_p2pmsg_zlib,
|
||||
'transmitted': self.transmitted_p2pmsg_zlib,
|
||||
},
|
||||
ARQ_SESSION_TYPES.p2p_connection: {
|
||||
'prepare': self.prepare_p2p_connection,
|
||||
|
@ -148,25 +149,39 @@ class ARQDataTypeHandler:
|
|||
decompressed_data = gzip.decompress(data)
|
||||
return decompressed_data
|
||||
|
||||
def prepare_p2pmsg_lzma(self, data):
|
||||
def prepare_p2pmsg_zlib(self, 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
|
||||
|
||||
def handle_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
self.log(f"Handling LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||
def handle_p2pmsg_zlib(self, data, statistics):
|
||||
decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
|
||||
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)
|
||||
return decompressed_data
|
||||
|
||||
def failed_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
self.log(f"Handling failed LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
|
||||
def failed_p2pmsg_zlib(self, data, statistics):
|
||||
decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
|
||||
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)
|
||||
return decompressed_data
|
||||
|
||||
def transmitted_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
def transmitted_p2pmsg_zlib(self, data, statistics):
|
||||
# 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)
|
||||
return decompressed_data
|
||||
|
||||
|
|
|
@ -30,6 +30,18 @@ class ARQSession:
|
|||
'duration_per_frame': 4.18,
|
||||
'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):
|
||||
|
@ -41,6 +53,8 @@ class ARQSession:
|
|||
self.states = state_manager
|
||||
self.states.setARQ(True)
|
||||
|
||||
self.protocol_version = 1
|
||||
|
||||
self.snr = []
|
||||
|
||||
self.dxcall = dxcall
|
||||
|
@ -152,6 +166,7 @@ class ARQSession:
|
|||
}
|
||||
|
||||
def update_histograms(self, confirmed_bytes, total_bytes):
|
||||
|
||||
stats = self.calculate_session_statistics(confirmed_bytes, total_bytes)
|
||||
self.snr_histogram.append(self.snr)
|
||||
self.bpm_histogram.append(stats['bytes_per_minute'])
|
||||
|
|
|
@ -81,6 +81,7 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.abort = False
|
||||
|
||||
def all_data_received(self):
|
||||
print(f"{self.total_length} vs {self.received_bytes}")
|
||||
return self.total_length == self.received_bytes
|
||||
|
||||
def final_crc_matches(self) -> bool:
|
||||
|
@ -108,11 +109,18 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
|
||||
self.event_manager.send_arq_session_new(
|
||||
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(
|
||||
self.id,
|
||||
self.dxcall,
|
||||
self.version,
|
||||
self.snr, flag_abort=self.abort)
|
||||
|
||||
self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
||||
if not self.abort:
|
||||
self.set_state(IRS_State.OPEN_ACK_SENT)
|
||||
|
@ -140,12 +148,14 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
return None, None
|
||||
|
||||
def process_incoming_data(self, frame):
|
||||
print(frame)
|
||||
if frame['offset'] != self.received_bytes:
|
||||
self.log(f"Discarding data offset {frame['offset']}")
|
||||
return False
|
||||
# TODO: IF WE HAVE AN OFFSET BECAUSE OF A SPEED LEVEL CHANGE FOR EXAMPLE,
|
||||
# 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
|
||||
|
||||
# Is this the last data part?
|
||||
if remaining_data_length <= len(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']
|
||||
|
||||
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.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))
|
||||
|
@ -171,41 +182,35 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.calibrate_speed_settings(burst_frame=burst_frame)
|
||||
ack = self.frame_factory.build_arq_burst_ack(
|
||||
self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_abort=self.abort
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
if self.final_crc_matches():
|
||||
self.log("All data received successfully!")
|
||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_final=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.session_ended = time.time()
|
||||
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
|
||||
else:
|
||||
|
||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_final=True,
|
||||
flag_checksum=False)
|
||||
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.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_TRANSFER = 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.type_byte = type_byte
|
||||
self.confirmed_bytes = 0
|
||||
self.expected_byte_offset = 0
|
||||
|
||||
self.state = ISS_State.NEW
|
||||
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:
|
||||
self.log("SENDING IN FALLBACK SPEED LEVEL", isWarning=True)
|
||||
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
|
||||
|
||||
self.set_state(ISS_State.FAILED)
|
||||
|
@ -105,9 +108,10 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
|
||||
def start(self):
|
||||
maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
|
||||
print(maximum_bandwidth)
|
||||
self.event_manager.send_arq_session_new(
|
||||
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.set_state(ISS_State.OPEN_SENT)
|
||||
|
||||
|
@ -136,8 +140,7 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
def send_info(self, irs_frame):
|
||||
# check if we received an abort flag
|
||||
if irs_frame["flag"]["ABORT"]:
|
||||
self.transmission_aborted(irs_frame)
|
||||
return
|
||||
return self.transmission_aborted(irs_frame)
|
||||
|
||||
info_frame = self.frame_factory.build_arq_session_info(self.id, self.total_length,
|
||||
helpers.get_crc_32(self.data),
|
||||
|
@ -148,16 +151,26 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
|
||||
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
|
||||
self.update_histograms(self.confirmed_bytes, self.total_length)
|
||||
|
||||
self.update_speed_level(irs_frame)
|
||||
if 'offset' in irs_frame:
|
||||
self.confirmed_bytes = irs_frame['offset']
|
||||
self.log(f"IRS confirmed {self.confirmed_bytes}/{self.total_length} bytes")
|
||||
self.event_manager.send_arq_session_progress(
|
||||
True, self.id, self.dxcall, self.confirmed_bytes, self.total_length, self.state.name, statistics=self.calculate_session_statistics(self.confirmed_bytes, self.total_length))
|
||||
|
||||
|
||||
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.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))
|
||||
|
||||
# check if we received an abort flag
|
||||
if irs_frame["flag"]["ABORT"]:
|
||||
|
@ -176,10 +189,14 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
burst = []
|
||||
for _ in range(0, self.frames_per_burst):
|
||||
offset = self.confirmed_bytes
|
||||
#self.expected_byte_offset = offset
|
||||
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(
|
||||
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)
|
||||
self.launch_twr(burst, self.TIMEOUT_TRANSFER, self.RETRIES_CONNECT, mode='auto', isARQBurst=True)
|
||||
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.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.arq_data_type_handler.state_manager.p2p_connection_sessions)
|
||||
#print(self.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.state_manager.remove_arq_iss_session(self.id)
|
||||
|
@ -222,9 +239,6 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
# break actual retries
|
||||
self.event_frame_received.set()
|
||||
|
||||
# start with abort sequence
|
||||
self.send_stop()
|
||||
|
||||
def send_stop(self):
|
||||
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)
|
||||
|
|
|
@ -210,6 +210,36 @@ def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray:
|
|||
RMS_COUNTER = 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:
|
||||
"""
|
||||
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
|
||||
|
||||
try:
|
||||
data = prepare_data_for_fft(data, target_length_samples=800)
|
||||
fftarray = np.fft.rfft(data)
|
||||
|
||||
# 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
|
||||
if fft_queue.qsize() >= 1:
|
||||
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:
|
||||
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
|
||||
|
||||
import ctypes
|
||||
from ctypes import *
|
||||
import hashlib
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
@ -25,12 +27,17 @@ class FREEDV_MODE(Enum):
|
|||
Enumeration for codec2 modes and names
|
||||
"""
|
||||
signalling = 19
|
||||
signalling_ack = 20
|
||||
datac0 = 14
|
||||
datac1 = 10
|
||||
datac3 = 12
|
||||
datac4 = 18
|
||||
datac13 = 19
|
||||
|
||||
datac14 = 20
|
||||
data_ofdm_500 = 21500
|
||||
data_ofdm_2438 = 2124381
|
||||
#data_qam_2438 = 2124382
|
||||
qam16c2 = 22
|
||||
|
||||
class FREEDV_MODE_USED_SLOTS(Enum):
|
||||
"""
|
||||
|
@ -43,9 +50,11 @@ class FREEDV_MODE_USED_SLOTS(Enum):
|
|||
datac3 = [False, False, True, False, False]
|
||||
datac4 = [False, False, True, False, False]
|
||||
datac13 = [False, False, True, False, False]
|
||||
fsk_ldpc = [False, False, True, False, False]
|
||||
fsk_ldpc_0 = [False, False, True, False, False]
|
||||
fsk_ldpc_1 = [False, False, True, False, False]
|
||||
datac14 = [False, False, True, False, False]
|
||||
data_ofdm_500 = [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
|
||||
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)
|
||||
else:
|
||||
files = []
|
||||
|
||||
api = None
|
||||
for file in files:
|
||||
try:
|
||||
|
@ -105,7 +113,7 @@ for file in files:
|
|||
if api is None or "api" not in locals():
|
||||
log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting")
|
||||
sys.exit(1)
|
||||
log.info("[C2 ] Libcodec2 loaded...")
|
||||
#log.info("[C2 ] Libcodec2 loaded...", path=file)
|
||||
# ctypes function init
|
||||
|
||||
# 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
|
||||
|
||||
# -------------------------------- 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_NC_MAX = 50 + 1 * 2
|
||||
|
@ -236,6 +186,8 @@ MODEM_STATS_MAX_F_HZ = 4000
|
|||
MODEM_STATS_MAX_F_EST = 4
|
||||
|
||||
|
||||
|
||||
|
||||
class MODEMSTATS(ctypes.Structure):
|
||||
"""Modem statistics structure"""
|
||||
|
||||
|
@ -421,33 +373,21 @@ class resampler:
|
|||
return out48
|
||||
|
||||
def open_instance(mode: int) -> ctypes.c_void_p:
|
||||
"""
|
||||
Return a codec2 instance of the type `mode`
|
||||
data_custom = 21
|
||||
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]:
|
||||
custom_params = ofdm_configurations[mode]
|
||||
return ctypes.cast(
|
||||
api.freedv_open_advanced(
|
||||
data_custom,
|
||||
ctypes.byref(custom_params),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
else:
|
||||
if mode not in [data_custom]:
|
||||
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
||||
|
||||
:param mode: Type of codec2 instance to return
|
||||
:type mode: Union[int, str]
|
||||
:return: C-function of the requested codec2 instance
|
||||
:rtype: ctypes.c_void_p
|
||||
"""
|
||||
# if mode in [FREEDV_MODE.fsk_ldpc_0.value]:
|
||||
# return ctypes.cast(
|
||||
# api.freedv_open_advanced(
|
||||
# FREEDV_MODE.fsk_ldpc.value,
|
||||
# ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
||||
# ),
|
||||
# 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)
|
||||
|
||||
def get_bytes_per_frame(mode: int) -> int:
|
||||
"""
|
||||
|
@ -462,3 +402,249 @@ def get_bytes_per_frame(mode: int) -> int:
|
|||
# TODO add close session
|
||||
# get number of bytes per frame for mode
|
||||
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):
|
||||
self.emit_event(event_queue)
|
||||
self.logger.info(self.log_message())
|
||||
frame = self.build_frame()
|
||||
return frame
|
||||
return self.build_frame()
|
||||
|
|
|
@ -3,7 +3,8 @@ from command import TxCommand
|
|||
class BeaconCommand(TxCommand):
|
||||
|
||||
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):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from command import TxCommand
|
||||
|
||||
from codec2 import FREEDV_MODE
|
||||
class CQCommand(TxCommand):
|
||||
|
||||
def build_frame(self):
|
||||
|
|
|
@ -41,7 +41,7 @@ class SendMessageCommand(TxCommand):
|
|||
# Convert JSON string to bytes (using UTF-8 encoding)
|
||||
payload = message.to_payload().encode('utf-8')
|
||||
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,
|
||||
modem,
|
||||
|
|
|
@ -48,7 +48,7 @@ enable_hmac = False
|
|||
enable_morse_identifier = False
|
||||
respond_to_cq = True
|
||||
tx_delay = 50
|
||||
maximum_bandwidth = 1700
|
||||
maximum_bandwidth = 2375
|
||||
enable_socket_interface = False
|
||||
|
||||
[SOCKET_INTERFACE]
|
||||
|
|
|
@ -94,7 +94,7 @@ class CONFIG:
|
|||
except Exception:
|
||||
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
|
||||
self.config_exists()
|
||||
|
|
|
@ -6,6 +6,7 @@ class DataFrameFactory:
|
|||
|
||||
LENGTH_SIG0_FRAME = 14
|
||||
LENGTH_SIG1_FRAME = 14
|
||||
LENGTH_ACK_FRAME = 3
|
||||
|
||||
"""
|
||||
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
|
||||
}
|
||||
|
||||
BEACON_FLAGS = {
|
||||
'AWAY_FROM_KEY': 0, # Bit-position for indicating the AWAY FROM KEY state
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
self.myfullcall = f"{config['STATION']['mycall']}-{config['STATION']['myssid']}"
|
||||
|
@ -27,7 +32,6 @@ class DataFrameFactory:
|
|||
|
||||
self._load_broadcast_templates()
|
||||
self._load_ping_templates()
|
||||
self._load_fec_templates()
|
||||
self._load_arq_templates()
|
||||
self._load_p2p_connection_templates()
|
||||
|
||||
|
@ -51,7 +55,8 @@ class DataFrameFactory:
|
|||
self.template_list[FR_TYPE.BEACON.value] = {
|
||||
"frame_length": self.LENGTH_SIG0_FRAME,
|
||||
"origin": 6,
|
||||
"gridsquare": 4
|
||||
"gridsquare": 4,
|
||||
"flag": 1
|
||||
}
|
||||
|
||||
def _load_ping_templates(self):
|
||||
|
@ -72,26 +77,6 @@ class DataFrameFactory:
|
|||
"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):
|
||||
|
||||
|
@ -101,6 +86,7 @@ class DataFrameFactory:
|
|||
"origin": 6,
|
||||
"session_id": 1,
|
||||
"maximum_bandwidth": 2,
|
||||
"protocol_version" : 1
|
||||
}
|
||||
|
||||
self.template_list[FR_TYPE.ARQ_SESSION_OPEN_ACK.value] = {
|
||||
|
@ -154,12 +140,12 @@ class DataFrameFactory:
|
|||
|
||||
# arq burst ack
|
||||
self.template_list[FR_TYPE.ARQ_BURST_ACK.value] = {
|
||||
"frame_length": self.LENGTH_SIG1_FRAME,
|
||||
"frame_length": self.LENGTH_ACK_FRAME,
|
||||
"session_id": 1,
|
||||
"offset":4,
|
||||
#"offset":4,
|
||||
"speed_level": 1,
|
||||
"frames_per_burst": 1,
|
||||
"snr": 1,
|
||||
#"frames_per_burst": 1,
|
||||
#"snr": 1,
|
||||
"flag": 1,
|
||||
}
|
||||
|
||||
|
@ -228,10 +214,13 @@ class DataFrameFactory:
|
|||
frame_length = frame_template["frame_length"]
|
||||
else:
|
||||
frame_length -= 2
|
||||
frame = bytearray(frame_length)
|
||||
frame[:1] = bytes([frametype.value])
|
||||
|
||||
buffer_position = 1
|
||||
frame = bytearray(frame_length)
|
||||
if frametype in [FR_TYPE.ARQ_BURST_ACK]:
|
||||
buffer_position = 0
|
||||
else:
|
||||
frame[:1] = bytes([frametype.value])
|
||||
buffer_position = 1
|
||||
for key, item_length in frame_template.items():
|
||||
if key == "frame_length":
|
||||
continue
|
||||
|
@ -242,17 +231,22 @@ class DataFrameFactory:
|
|||
raise OverflowError("Frame data overflow!")
|
||||
frame[buffer_position: buffer_position + item_length] = content[key]
|
||||
buffer_position += item_length
|
||||
|
||||
return frame
|
||||
|
||||
def deconstruct(self, frame):
|
||||
buffer_position = 1
|
||||
# Extract frametype and get the corresponding template
|
||||
frametype = int.from_bytes(frame[:1], "big")
|
||||
frame_template = self.template_list.get(frametype)
|
||||
def deconstruct(self, frame, mode_name=None):
|
||||
|
||||
if not frame_template:
|
||||
# Handle the case where the frame type is not recognized
|
||||
raise ValueError(f"Unknown frame type: {frametype}")
|
||||
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
|
||||
frametype = int.from_bytes(frame[:1], "big")
|
||||
frame_template = self.template_list.get(frametype)
|
||||
|
||||
extracted_data = {"frame_type": FR_TYPE(frametype).name, "frame_type_int": frametype}
|
||||
|
||||
|
@ -262,6 +256,7 @@ class DataFrameFactory:
|
|||
|
||||
# data is always on the last payload slots
|
||||
if item_length in ["dynamic"] and key in["data"]:
|
||||
print(len(frame))
|
||||
data = frame[buffer_position:-2]
|
||||
item_length = len(data)
|
||||
else:
|
||||
|
@ -279,7 +274,7 @@ class DataFrameFactory:
|
|||
|
||||
elif key in ["session_id", "speed_level",
|
||||
"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')
|
||||
|
||||
elif key in ["snr"]:
|
||||
|
@ -289,7 +284,6 @@ class DataFrameFactory:
|
|||
|
||||
data = int.from_bytes(data, "big")
|
||||
extracted_data[key] = {}
|
||||
|
||||
# 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]:
|
||||
flag_dict = self.ARQ_FLAGS
|
||||
|
@ -297,6 +291,15 @@ class DataFrameFactory:
|
|||
# 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)
|
||||
|
||||
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:
|
||||
extracted_data[key] = data
|
||||
|
||||
|
@ -350,10 +353,16 @@ class DataFrameFactory:
|
|||
}
|
||||
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 = {
|
||||
"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)
|
||||
|
||||
|
@ -388,12 +397,13 @@ class DataFrameFactory:
|
|||
test_frame[:1] = bytes([FR_TYPE.TEST_FRAME.value])
|
||||
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 = {
|
||||
"destination_crc": helpers.get_crc_24(destination),
|
||||
"origin": helpers.callsign_to_bytes(self.myfullcall),
|
||||
"session_id": session_id.to_bytes(1, '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)
|
||||
|
||||
|
@ -467,8 +477,7 @@ class DataFrameFactory:
|
|||
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,
|
||||
frames_per_burst: int, snr: int, flag_final=False, flag_checksum=False, flag_abort=False):
|
||||
def build_arq_burst_ack(self, session_id: bytes, speed_level: int, flag_final=False, flag_checksum=False, flag_abort=False):
|
||||
flag = 0b00000000
|
||||
if flag_final:
|
||||
flag = helpers.set_flag(flag, 'FINAL', True, self.ARQ_FLAGS)
|
||||
|
@ -481,10 +490,7 @@ class DataFrameFactory:
|
|||
|
||||
payload = {
|
||||
"session_id": session_id.to_bytes(1, 'big'),
|
||||
"offset": offset.to_bytes(4, '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'),
|
||||
}
|
||||
return self.construct(FR_TYPE.ARQ_BURST_ACK, payload)
|
||||
|
|
|
@ -13,17 +13,17 @@ class Demodulator():
|
|||
MODE_DICT = {}
|
||||
# Iterate over the FREEDV_MODE enum members
|
||||
for mode in codec2.FREEDV_MODE:
|
||||
MODE_DICT[mode.value] = {
|
||||
'decode': False,
|
||||
'bytes_per_frame': None,
|
||||
'bytes_out': None,
|
||||
'audio_buffer': None,
|
||||
'nin': None,
|
||||
'instance': None,
|
||||
'state_buffer': [],
|
||||
'name': mode.name.upper(),
|
||||
'decoding_thread': None
|
||||
}
|
||||
MODE_DICT[mode.value] = {
|
||||
'decode': False,
|
||||
'bytes_per_frame': None,
|
||||
'bytes_out': None,
|
||||
'audio_buffer': None,
|
||||
'nin': None,
|
||||
'instance': None,
|
||||
'state_buffer': [],
|
||||
'name': mode.name.upper(),
|
||||
'decoding_thread': None
|
||||
}
|
||||
|
||||
def __init__(self, config, audio_rx_q, data_q_rx, states, event_manager, service_queue, fft_queue):
|
||||
self.log = structlog.get_logger("Demodulator")
|
||||
|
@ -49,6 +49,10 @@ class Demodulator():
|
|||
|
||||
# enable decoding of signalling modes
|
||||
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(
|
||||
target=self.tci_rx_callback,
|
||||
|
@ -69,15 +73,13 @@ class Demodulator():
|
|||
"""
|
||||
|
||||
# create codec2 instance
|
||||
c2instance = ctypes.cast(
|
||||
codec2.api.freedv_open(mode), ctypes.c_void_p
|
||||
)
|
||||
#c2instance = ctypes.cast(
|
||||
c2instance = codec2.open_instance(mode)
|
||||
|
||||
# get bytes per frame
|
||||
bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
||||
)
|
||||
|
||||
# create byte out buffer
|
||||
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||
|
||||
|
@ -186,7 +188,7 @@ class Demodulator():
|
|||
nin = codec2.api.freedv_nin(freedv)
|
||||
if nbytes == bytes_per_frame:
|
||||
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)
|
||||
self.get_scatter(freedv)
|
||||
|
@ -197,7 +199,9 @@ class Demodulator():
|
|||
'bytes_per_frame': bytes_per_frame,
|
||||
'snr': snr,
|
||||
'frequency_offset': self.get_frequency_offset(freedv),
|
||||
'mode_name': mode_name
|
||||
}
|
||||
|
||||
self.data_queue_received.put(item)
|
||||
|
||||
|
||||
|
@ -344,6 +348,7 @@ class Demodulator():
|
|||
|
||||
# signalling is always 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
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.datac4.value]["decode"] = True
|
||||
|
|
|
@ -36,6 +36,7 @@ class explorer():
|
|||
bandwidth = str(self.config['MODEM']['maximum_bandwidth'])
|
||||
beacon = str(self.states.is_beacon_running)
|
||||
strength = str(self.states.s_meter_strength)
|
||||
away_from_key = str(self.states.is_away_from_key)
|
||||
|
||||
# stop pushing if default callsign
|
||||
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)
|
||||
|
||||
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:
|
||||
try:
|
||||
|
|
|
@ -46,9 +46,9 @@ class DISPATCHER():
|
|||
FR_TYPE.PING_ACK.value: {"class": FrameHandler, "name": "PING ACK"},
|
||||
FR_TYPE.PING.value: {"class": PingFrameHandler, "name": "PING"},
|
||||
FR_TYPE.QRV.value: {"class": FrameHandler, "name": "QRV"},
|
||||
FR_TYPE.IS_WRITING.value: {"class": FrameHandler, "name": "IS_WRITING"},
|
||||
FR_TYPE.FEC.value: {"class": FrameHandler, "name": "FEC"},
|
||||
FR_TYPE.FEC_WAKEUP.value: {"class": FrameHandler, "name": "FEC WAKEUP"},
|
||||
#FR_TYPE.IS_WRITING.value: {"class": FrameHandler, "name": "IS_WRITING"},
|
||||
#FR_TYPE.FEC.value: {"class": FrameHandler, "name": "FEC"},
|
||||
#FR_TYPE.FEC_WAKEUP.value: {"class": FrameHandler, "name": "FEC WAKEUP"},
|
||||
}
|
||||
|
||||
def __init__(self, config, event_manager, states, modem):
|
||||
|
@ -79,17 +79,18 @@ class DISPATCHER():
|
|||
"""Queue received data for processing"""
|
||||
while True:
|
||||
data = self.data_queue_received.get()
|
||||
self.new_process_data(
|
||||
self.process_data(
|
||||
data['payload'],
|
||||
data['freedv'],
|
||||
data['bytes_per_frame'],
|
||||
data['snr'],
|
||||
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
|
||||
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"]
|
||||
|
||||
if frametype not in self.FRAME_HANDLER:
|
||||
|
|
|
@ -101,6 +101,9 @@ class FrameHandler():
|
|||
if "session_id" in frame:
|
||||
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)
|
||||
|
||||
def add_to_heard_stations(self):
|
||||
|
@ -127,7 +130,8 @@ class FrameHandler():
|
|||
self.states.radio_frequency,
|
||||
self.states.heard_stations,
|
||||
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):
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ class CQFrameHandler(frame_handler.FrameHandler):
|
|||
# return bool(self.config['MODEM']['respond_to_cq'] and not self.states.getARQ())
|
||||
|
||||
def follow_protocol(self):
|
||||
|
||||
if self.states.getARQ():
|
||||
return
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ import time
|
|||
|
||||
|
||||
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:
|
||||
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.
|
||||
distance_km (float): Distance to the DX station in kilometers.
|
||||
distance_miles (float): Distance to the DX station in miles.
|
||||
away_from_key (bool): Away from key indicator
|
||||
|
||||
Returns:
|
||||
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
|
||||
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
|
||||
|
|
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,
|
||||
device=out_dev_index,
|
||||
samplerate=self.AUDIO_SAMPLE_RATE,
|
||||
blocksize=4800,
|
||||
blocksize=1200,
|
||||
)
|
||||
self.sd_output_stream.start()
|
||||
|
||||
|
@ -216,7 +216,6 @@ class RF:
|
|||
) -> bool:
|
||||
|
||||
self.demodulator.reset_data_sync()
|
||||
|
||||
# Wait for some other thread that might be transmitting
|
||||
self.states.waitForTransmission()
|
||||
self.states.setTransmitting(True)
|
||||
|
@ -234,6 +233,8 @@ class RF:
|
|||
else:
|
||||
txbuffer_out = x
|
||||
|
||||
|
||||
|
||||
# transmit audio
|
||||
self.enqueue_audio_out(txbuffer_out)
|
||||
|
||||
|
@ -245,11 +246,11 @@ class RF:
|
|||
|
||||
def enqueue_audio_out(self, audio_48k) -> None:
|
||||
self.enqueuing_audio = True
|
||||
|
||||
if not self.states.isTransmitting():
|
||||
self.states.setTransmitting(True)
|
||||
|
||||
self.radio.set_ptt(True)
|
||||
|
||||
self.event_manager.send_ptt_change(True)
|
||||
|
||||
if self.radiocontrol in ["tci"]:
|
||||
|
@ -258,7 +259,7 @@ class RF:
|
|||
self.tci_module.wait_until_transmitted(audio_48k)
|
||||
else:
|
||||
# slice audio data to needed blocklength
|
||||
block_size = 4800
|
||||
block_size = self.sd_output_stream.blocksize
|
||||
pad_length = -len(audio_48k) % block_size
|
||||
padded_data = np.pad(audio_48k, (0, pad_length), mode='constant')
|
||||
sliced_audio_data = padded_data.reshape(-1, block_size)
|
||||
|
@ -266,6 +267,7 @@ class RF:
|
|||
for block in sliced_audio_data:
|
||||
self.audio_out_queue.put(block)
|
||||
|
||||
|
||||
self.enqueuing_audio = False
|
||||
self.states.transmitting_event.wait()
|
||||
|
||||
|
@ -275,16 +277,18 @@ class RF:
|
|||
return
|
||||
|
||||
def sd_output_audio_callback(self, outdata: np.ndarray, frames: int, time, status) -> None:
|
||||
|
||||
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()
|
||||
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)
|
||||
|
||||
else:
|
||||
# reset transmitting state only, if we are not actively processing audio
|
||||
# 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)
|
||||
# Fill with zeros if the queue is empty
|
||||
outdata.fill(0)
|
||||
|
@ -325,4 +329,3 @@ class RF:
|
|||
audiobuffer.push(audio_8k_level_adjusted)
|
||||
except Exception as 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_DISCONNECT = 36
|
||||
P2P_CONNECTION_DISCONNECT_ACK = 37
|
||||
MESH_BROADCAST = 100
|
||||
MESH_SIGNALLING_PING = 101
|
||||
MESH_SIGNALLING_PING_ACK = 102
|
||||
#MESH_BROADCAST = 100
|
||||
#MESH_SIGNALLING_PING = 101
|
||||
#MESH_SIGNALLING_PING_ACK = 102
|
||||
CQ = 200
|
||||
QRV = 201
|
||||
PING = 210
|
||||
PING_ACK = 211
|
||||
IS_WRITING = 215
|
||||
#IS_WRITING = 215
|
||||
BEACON = 250
|
||||
FEC = 251
|
||||
FEC_WAKEUP = 252
|
||||
#FEC = 251
|
||||
#FEC_WAKEUP = 252
|
||||
IDENT = 254
|
||||
TEST_FRAME = 255
|
||||
|
|
|
@ -23,6 +23,11 @@ class Modulator:
|
|||
self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.value)
|
||||
self.freedv_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value)
|
||||
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
|
||||
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):
|
||||
# Init buffer for preample
|
||||
|
@ -82,7 +87,6 @@ class Modulator:
|
|||
crc = crc.value.to_bytes(2, byteorder="big")
|
||||
# Append CRC to data buffer
|
||||
buffer += crc
|
||||
|
||||
assert (bytes_per_frame == len(buffer))
|
||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||
# modulate DATA and save it into mod_out pointer
|
||||
|
@ -107,19 +111,25 @@ class Modulator:
|
|||
|
||||
# get freedv instance by mode
|
||||
mode_transition = {
|
||||
codec2.FREEDV_MODE.signalling_ack: self.freedv_datac14_tx,
|
||||
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
||||
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
||||
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
||||
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
||||
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
||||
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
||||
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:
|
||||
freedv = mode_transition[mode]
|
||||
else:
|
||||
print("wrong mode.................")
|
||||
print(mode)
|
||||
return False
|
||||
#return False
|
||||
|
||||
|
||||
# Open codec2 instance
|
||||
|
|
|
@ -33,7 +33,7 @@ from schedule_manager import ScheduleManager
|
|||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
sock = Sock(app)
|
||||
MODEM_VERSION = "0.14.5-alpha"
|
||||
MODEM_VERSION = "0.15.2-alpha"
|
||||
|
||||
# set config file to use
|
||||
def set_config():
|
||||
|
@ -146,14 +146,15 @@ def post_cqcqcq():
|
|||
def post_beacon():
|
||||
if request.method not in ['POST']:
|
||||
return api_response({"info": "endpoint for controlling BEACON STATE via POST"})
|
||||
|
||||
if not isinstance(request.json['enabled'], bool):
|
||||
if not isinstance(request.json['enabled'], bool) or not isinstance(request.json['away_from_key'], bool):
|
||||
api_abort(f"Incorrect value for 'enabled'. Shoud be bool.")
|
||||
if not app.state_manager.is_modem_running:
|
||||
api_abort('Modem not running', 503)
|
||||
|
||||
if not app.state_manager.is_beacon_running:
|
||||
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():
|
||||
enqueue_tx_command(command_beacon.BeaconCommand, request.json)
|
||||
else:
|
||||
|
@ -326,18 +327,21 @@ def sock_states(sock):
|
|||
|
||||
@atexit.register
|
||||
def stop_server():
|
||||
print("------------------------------------------")
|
||||
try:
|
||||
app.service_manager.modem_service.put("stop")
|
||||
app.socket_interface_manager.stop_servers()
|
||||
if app.socket_interface_manager:
|
||||
app.socket_interface_manager.stop_servers()
|
||||
|
||||
if app.service_manager.modem:
|
||||
app.service_manager.modem.sd_input_stream.stop
|
||||
audio.sd._terminate()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Error stopping modem")
|
||||
time.sleep(1)
|
||||
print("------------------------------------------")
|
||||
print('Server shutdown...')
|
||||
print("------------------------------------------")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 10}
|
||||
|
|
|
@ -40,7 +40,7 @@ class SM:
|
|||
elif cmd in ['stop'] and self.modem:
|
||||
self.stop_modem()
|
||||
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()
|
||||
# we need to wait a bit for avoiding a portaudio crash
|
||||
threading.Event().wait(0.5)
|
||||
|
@ -48,7 +48,8 @@ class SM:
|
|||
elif cmd in ['restart']:
|
||||
self.stop_modem()
|
||||
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()
|
||||
|
||||
# we need to wait a bit for avoiding a portaudio crash
|
||||
|
|
|
@ -23,6 +23,7 @@ class StateManager:
|
|||
self.setARQ(False)
|
||||
|
||||
self.is_beacon_running = False
|
||||
self.is_away_from_key = False
|
||||
|
||||
# If true, any wait() call is blocking
|
||||
self.transmitting_event = threading.Event()
|
||||
|
@ -84,6 +85,7 @@ class StateManager:
|
|||
"type": msgtype,
|
||||
"is_modem_running": self.is_modem_running,
|
||||
"is_beacon_running": self.is_beacon_running,
|
||||
"is_away_from_key": self.is_away_from_key,
|
||||
"radio_status": self.radio_status,
|
||||
"radio_frequency": self.radio_frequency,
|
||||
"radio_mode": self.radio_mode,
|
||||
|
@ -184,7 +186,6 @@ class StateManager:
|
|||
# if frequency not provided, add it here
|
||||
if 'frequency' not in activity_data:
|
||||
activity_data['frequency'] = self.radio_frequency
|
||||
|
||||
self.activities_list[activity_id] = activity_data
|
||||
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_postamble_modem_samples(c2instance)
|
||||
time = samples / 8000
|
||||
#print(mode)
|
||||
#if mode == codec2.FREEDV_MODE.signalling:
|
||||
# time = 0.69
|
||||
#print(time)
|
||||
return time
|
||||
|
||||
def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray) -> bool:
|
||||
|
@ -82,7 +86,7 @@ class TestARQSession(unittest.TestCase):
|
|||
cls.irs_modem)
|
||||
|
||||
# Frame loss probability in %
|
||||
cls.loss_probability = 30
|
||||
cls.loss_probability = 0
|
||||
|
||||
cls.channels_running = True
|
||||
|
||||
|
@ -91,12 +95,18 @@ class TestARQSession(unittest.TestCase):
|
|||
# Transfer data between both parties
|
||||
try:
|
||||
transmission = modem_transmit_queue.get(timeout=1)
|
||||
transmission["bytes"] += bytes(2) # simulate 2 bytes crc checksum
|
||||
if random.randint(0, 100) < self.loss_probability:
|
||||
self.logger.info(f"[{threading.current_thread().name}] Frame lost...")
|
||||
continue
|
||||
|
||||
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:
|
||||
continue
|
||||
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.channels_running = False
|
||||
|
||||
def testARQSessionSmallPayload(self):
|
||||
def DisabledtestARQSessionSmallPayload(self):
|
||||
# set Packet Error Rate (PER) / frame loss probability
|
||||
self.loss_probability = 30
|
||||
|
||||
|
@ -160,7 +170,7 @@ class TestARQSession(unittest.TestCase):
|
|||
self.waitAndCloseChannels()
|
||||
del cmd
|
||||
|
||||
def testARQSessionAbortTransmissionISS(self):
|
||||
def DisabledtestARQSessionAbortTransmissionISS(self):
|
||||
# set Packet Error Rate (PER) / frame loss probability
|
||||
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.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:
|
||||
self.iss_state_manager.arq_iss_sessions[id].abort_transmission()
|
||||
|
||||
self.waitAndCloseChannels()
|
||||
del cmd
|
||||
|
||||
def testARQSessionAbortTransmissionIRS(self):
|
||||
def DisabledtestARQSessionAbortTransmissionIRS(self):
|
||||
# set Packet Error Rate (PER) / frame loss probability
|
||||
self.loss_probability = 0
|
||||
|
||||
|
@ -198,7 +208,7 @@ class TestARQSession(unittest.TestCase):
|
|||
self.waitAndCloseChannels()
|
||||
del cmd
|
||||
|
||||
def testSessionCleanupISS(self):
|
||||
def DisabledtestSessionCleanupISS(self):
|
||||
|
||||
params = {
|
||||
'dxcall': "AA1AAA-1",
|
||||
|
@ -217,7 +227,7 @@ class TestARQSession(unittest.TestCase):
|
|||
break
|
||||
del cmd
|
||||
|
||||
def testSessionCleanupIRS(self):
|
||||
def DisabledtestSessionCleanupIRS(self):
|
||||
session = arq_session_irs.ARQSessionIRS(self.config,
|
||||
self.irs_modem,
|
||||
'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