Merge pull request #704 from DJ2LS/dev-datac14

WIP: New speed levels & compression & signalling mode
This commit is contained in:
DJ2LS 2024-04-17 22:31:57 +02:00 committed by GitHub
commit eab145fc8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 913 additions and 311 deletions

View file

@ -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: |

View file

@ -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",

View file

@ -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 =

View file

@ -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
];

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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"

View file

@ -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"

View 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>

View file

@ -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"

View file

@ -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>

View file

@ -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() {

View file

@ -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":

View file

@ -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,

3
modem/.gitignore vendored
View file

@ -130,3 +130,6 @@ dmypy.json
# FreeDATA config
config.ini
#FreeData DB
freedata-messages.db

View file

@ -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

View file

@ -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'])

View file

@ -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)

View file

@ -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']
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))
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)

View file

@ -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}")

View file

@ -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,34 +373,22 @@ class resampler:
return out48
def open_instance(mode: int) -> ctypes.c_void_p:
"""
Return a codec2 instance of the type `mode`
: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,
# )
#
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)
def get_bytes_per_frame(mode: int) -> int:
"""
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
# 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
}

View file

@ -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()

View file

@ -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):

View file

@ -1,5 +1,5 @@
from command import TxCommand
from codec2 import FREEDV_MODE
class CQCommand(TxCommand):
def build_frame(self):

View file

@ -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,

View file

@ -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]

View file

@ -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()

View file

@ -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,9 +214,12 @@ class DataFrameFactory:
frame_length = frame_template["frame_length"]
else:
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
for key, item_length in frame_template.items():
if key == "frame_length":
@ -242,18 +231,23 @@ 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):
def deconstruct(self, frame, mode_name=None):
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)
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}
for key, item_length in frame_template.items():
@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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")
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}

View file

@ -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

View file

@ -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()

View file

@ -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',

View 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)