mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge remote-tracking branch 'origin/pep8_improvements' into pep8_improvements
This commit is contained in:
commit
1bab085ca8
40 changed files with 3707 additions and 1811 deletions
17
.github/workflows/build-project-linux.yml
vendored
17
.github/workflows/build-project-linux.yml
vendored
|
@ -2,7 +2,7 @@ name: Linux
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_linux_release:
|
build_linux_release:
|
||||||
|
@ -27,8 +27,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
run: |
|
run: |
|
||||||
|
@ -44,7 +42,6 @@ jobs:
|
||||||
pip3 install structlog
|
pip3 install structlog
|
||||||
pip3 install sounddevice
|
pip3 install sounddevice
|
||||||
|
|
||||||
|
|
||||||
#- name: Build Hamlib Python Binding
|
#- name: Build Hamlib Python Binding
|
||||||
# if: matrix.os == 'ubuntu-latest'
|
# if: matrix.os == 'ubuntu-latest'
|
||||||
# working-directory: tnc
|
# working-directory: tnc
|
||||||
|
@ -58,19 +55,15 @@ jobs:
|
||||||
# make
|
# make
|
||||||
# make install
|
# make install
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Build codec2 Linux
|
- name: Build codec2 Linux
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/drowe67/codec2.git
|
git clone https://github.com/drowe67/codec2.git
|
||||||
cd codec2 && mkdir build_linux && cd build_linux
|
cd codec2 && git checkout master && mkdir build_linux && cd build_linux
|
||||||
cmake ../
|
cmake ../
|
||||||
make
|
make
|
||||||
|
|
||||||
|
|
||||||
- name: Build Linux Daemon
|
- name: Build Linux Daemon
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
|
@ -82,7 +75,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
ls -R
|
ls -R
|
||||||
|
|
||||||
|
|
||||||
- name: Compress Linux
|
- name: Compress Linux
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -96,8 +88,6 @@ jobs:
|
||||||
name: tnc-artifact
|
name: tnc-artifact
|
||||||
path: ./tnc/dist/compressed/*
|
path: ./tnc/dist/compressed/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Copy TNC to GUI Linux
|
- name: Copy TNC to GUI Linux
|
||||||
if: matrix.os == 'ubuntu-20.04'
|
if: matrix.os == 'ubuntu-20.04'
|
||||||
run: |
|
run: |
|
||||||
|
@ -124,6 +114,3 @@ jobs:
|
||||||
# release the app after building
|
# release the app after building
|
||||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
args: "-p always"
|
args: "-p always"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
15
.github/workflows/build-project-mac.yml
vendored
15
.github/workflows/build-project-mac.yml
vendored
|
@ -2,7 +2,7 @@ name: macOS
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_linux_release:
|
build_linux_release:
|
||||||
|
@ -27,8 +27,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Install macOS dependencies
|
- name: Install macOS dependencies
|
||||||
if: matrix.os == 'macos-10.15'
|
if: matrix.os == 'macos-10.15'
|
||||||
run: |
|
run: |
|
||||||
|
@ -48,17 +46,15 @@ jobs:
|
||||||
brew install portaudio
|
brew install portaudio
|
||||||
pip3 install pyaudio
|
pip3 install pyaudio
|
||||||
|
|
||||||
|
|
||||||
- name: Build codec2 macOS
|
- name: Build codec2 macOS
|
||||||
if: matrix.os == 'macos-10.15'
|
if: matrix.os == 'macos-10.15'
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/drowe67/codec2.git
|
git clone https://github.com/drowe67/codec2.git
|
||||||
cd codec2 && mkdir build_mac && cd build_mac
|
cd codec2 && git checkout master && mkdir build_mac && cd build_mac
|
||||||
cmake ../
|
cmake ../
|
||||||
make
|
make
|
||||||
|
|
||||||
|
|
||||||
- name: Build macOS pyinstaller
|
- name: Build macOS pyinstaller
|
||||||
if: matrix.os == 'macos-10.15'
|
if: matrix.os == 'macos-10.15'
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
|
@ -70,7 +66,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
ls -R
|
ls -R
|
||||||
|
|
||||||
|
|
||||||
- name: Compress
|
- name: Compress
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -84,9 +79,6 @@ jobs:
|
||||||
name: tnc-artifact
|
name: tnc-artifact
|
||||||
path: ./tnc/dist/compressed/*
|
path: ./tnc/dist/compressed/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Copy TNC to GUI
|
- name: Copy TNC to GUI
|
||||||
if: matrix.os == 'macos-10.15'
|
if: matrix.os == 'macos-10.15'
|
||||||
run: |
|
run: |
|
||||||
|
@ -113,6 +105,3 @@ jobs:
|
||||||
# release the app after building
|
# release the app after building
|
||||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
args: "-p always"
|
args: "-p always"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
4
.github/workflows/ctest.yml
vendored
4
.github/workflows/ctest.yml
vendored
|
@ -18,13 +18,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio
|
sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio
|
||||||
pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio sounddevice
|
pip3 install psutil crcengine ujson pyserial numpy structlog miniaudio sounddevice pytest
|
||||||
|
|
||||||
- name: Build codec2
|
- name: Build codec2
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/drowe67/codec2.git
|
git clone https://github.com/drowe67/codec2.git
|
||||||
cd codec2 && git checkout dr-tnc && git pull
|
cd codec2 && git checkout master # This should be pinned to a release
|
||||||
mkdir -p build_linux && cd build_linux && cmake .. && make
|
mkdir -p build_linux && cd build_linux && cmake .. && make
|
||||||
|
|
||||||
- name: run ctests
|
- name: run ctests
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,3 +6,4 @@ tnc/codec2
|
||||||
**/Testing
|
**/Testing
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -24,16 +24,80 @@ set(TESTFRAMES 3)
|
||||||
|
|
||||||
add_test(NAME audio_buffer
|
add_test(NAME audio_buffer
|
||||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
python3 test_audiobuffer.py")
|
python3 test_audiobuffer.py")
|
||||||
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||||
|
|
||||||
add_test(NAME resampler
|
add_test(NAME resampler
|
||||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
python3 t48_8_short.py")
|
python3 test_resample_48_8.py")
|
||||||
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
|
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
|
||||||
|
|
||||||
|
add_test(NAME tnc_state_machine
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_tnc_states.py")
|
||||||
|
set_tests_properties(tnc_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||||
|
|
||||||
|
add_test(NAME tnc_irs_iss
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_tnc.py")
|
||||||
|
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||||
|
|
||||||
|
add_test(NAME helper_routines
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_helpers.py")
|
||||||
|
set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||||
|
|
||||||
|
add_test(NAME py_highsnr_stdio_P_P_multi
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
export BURSTS=${BURSTS};
|
||||||
|
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||||
|
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_highsnr_stdio_P_P_multi.py")
|
||||||
|
set_tests_properties(py_highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||||
|
|
||||||
|
add_test(NAME py_highsnr_stdio_P_P_datacx
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
export BURSTS=${BURSTS};
|
||||||
|
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||||
|
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_highsnr_stdio_P_P_datacx.py")
|
||||||
|
set_tests_properties(py_highsnr_stdio_P_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||||
|
|
||||||
|
add_test(NAME py_highsnr_stdio_P_C_datacx
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
export BURSTS=${BURSTS};
|
||||||
|
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||||
|
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_highsnr_stdio_P_C_datacx.py")
|
||||||
|
set_tests_properties(py_highsnr_stdio_P_C_datacx PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
|
||||||
|
|
||||||
|
add_test(NAME py_highsnr_stdio_C_P_datacx
|
||||||
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
|
export PYTHONPATH=../tnc;
|
||||||
|
export BURSTS=${BURSTS};
|
||||||
|
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||||
|
export TESTFRAMES=${TESTFRAMES};
|
||||||
|
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||||
|
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||||
|
python3 test_highsnr_stdio_C_P_datacx.py")
|
||||||
|
set_tests_properties(py_highsnr_stdio_C_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||||
|
|
||||||
add_test(NAME highsnr_stdio_P_C_single
|
add_test(NAME highsnr_stdio_P_C_single
|
||||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||||
|
@ -68,7 +132,6 @@ add_test(NAME highsnr_stdio_P_P_multi
|
||||||
python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 20")
|
python3 test_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 20")
|
||||||
set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||||
|
|
||||||
|
|
||||||
# These tests can't run on GitHub actions as we don't have a virtual sound card
|
# These tests can't run on GitHub actions as we don't have a virtual sound card
|
||||||
if(NOT DEFINED ENV{GITHUB_RUN_ID})
|
if(NOT DEFINED ENV{GITHUB_RUN_ID})
|
||||||
|
|
||||||
|
|
|
@ -30,4 +30,3 @@ Download the latest developer release from the releases section, unpack it and j
|
||||||
## Installation
|
## Installation
|
||||||
Please check the [wiki](https://wiki.freedata.app) for installation instructions
|
Please check the [wiki](https://wiki.freedata.app) for installation instructions
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "FreeDATA",
|
"name": "FreeDATA",
|
||||||
"version": "0.4.0-alpha.8",
|
"version": "0.4.0-alpha.9",
|
||||||
"description": "FreeDATA ",
|
"description": "FreeDATA ",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -511,8 +511,9 @@ update_chat = function(obj) {
|
||||||
`;
|
`;
|
||||||
var controlarea_receive = '';
|
var controlarea_receive = '';
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.log("error with database parsing...")
|
console.log("error with database parsing...")
|
||||||
|
console.log(err)
|
||||||
}
|
}
|
||||||
// CALLSIGN LIST
|
// CALLSIGN LIST
|
||||||
if (!(document.getElementById('chat-' + dxcallsign + '-list'))) {
|
if (!(document.getElementById('chat-' + dxcallsign + '-list'))) {
|
||||||
|
|
|
@ -1,4 +1,52 @@
|
||||||
|
|
||||||
|
# Unit Test Menu
|
||||||
|
|
||||||
|
The following `CTest` tests cover some TNC functionality and the interface to codec2:
|
||||||
|
1. Name: `audio_buffer`
|
||||||
|
Tests the thread safety of the audio buffer routines.
|
||||||
|
1. Name: `resampler`
|
||||||
|
Tests FreeDATA audio resampling from 48KHz to 8KHz.
|
||||||
|
1. Name: `tnc_state_machine`
|
||||||
|
Tests TNC transitions between states.
|
||||||
|
1. Name: `helper_routines`
|
||||||
|
Tests various helper routines.
|
||||||
|
1. Name: `py_highsnr_stdio_P_P_multi`
|
||||||
|
Tests a high signal-to-noise ratio (good quality) audio path using multiple codecs. (Pure python.)
|
||||||
|
1. Name: `py_highsnr_stdio_P_P_datacx`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
|
||||||
|
1. Name: `py_highsnr_stdio_P_C_datacx`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
|
||||||
|
1. Name: `py_highsnr_stdio_C_P_datacx`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple individual codecs.
|
||||||
|
1. Name: `highsnr_stdio_P_C_single`
|
||||||
|
Tests compatibility with FreeDATA's transmit and freedv's raw data receive.
|
||||||
|
1. Name: `highsnr_stdio_C_P_single`
|
||||||
|
Tests compatibility with freedv's raw data transmit and FreeDATA's receive.
|
||||||
|
1. Name: `highsnr_stdio_P_P_single`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.)
|
||||||
|
1. Name: `highsnr_stdio_P_P_multi`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.)
|
||||||
|
|
||||||
|
The following tests can not currently be run with GitHub's pipeline as they require the ALSA dummy device
|
||||||
|
kernel module to be installed. They also do not perform reliably. These tests are slowly being
|
||||||
|
replaced with equivalent pipeline-compatible tests.
|
||||||
|
1. Name: `highsnr_virtual1_P_P_single_alsa`
|
||||||
|
Tests a high signal-to-noise ratio audio path using a single codec directly over an ALSA dummy device.
|
||||||
|
1. Name: `highsnr_virtual2_P_P_single`
|
||||||
|
Tests a high signal-to-noise ratio audio path using a single codec over an ALSA dummy device.
|
||||||
|
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
|
||||||
|
1. Name: `highsnr_virtual3_P_P_multi`
|
||||||
|
Tests a high signal-to-noise ratio audio path using multiple codecs over an ALSA dummy device.
|
||||||
|
1. Name: `highsnr_virtual4_P_P_single_callback`
|
||||||
|
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
|
||||||
|
1. Name: `highsnr_virtual4_P_P_single_callback_outside`
|
||||||
|
**Not functional** due to an incompatibility between the two scripts in the way they determine audio devices.
|
||||||
|
1. Name: `highsnr_virtual5_P_P_multi_callback`
|
||||||
|
1. Name: `highsnr_virtual5_P_P_multi_callback_outside`
|
||||||
|
1. Name: `highsnr_ARQ_short`
|
||||||
|
**Not functional**, it is an obsolete or not yet completed test.
|
||||||
|
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
|
|
||||||
1. Install:
|
1. Install:
|
||||||
|
|
175
test/ping.py
175
test/ping.py
|
@ -1,33 +1,32 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import ctypes
|
||||||
|
import pathlib
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
import pyaudio
|
||||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int)
|
|
||||||
parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int)
|
|
||||||
parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=0, type=int)
|
|
||||||
parser.add_argument('--txmode', dest="FREEDV_TX_MODE", default=0, type=int)
|
|
||||||
parser.add_argument('--rxmode', dest="FREEDV_RX_MODE", default=0, type=int)
|
|
||||||
parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int)
|
|
||||||
parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int)
|
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description="Simons TEST TNC")
|
||||||
|
parser.add_argument("--bursts", dest="N_BURSTS", default=0, type=int)
|
||||||
|
parser.add_argument("--frames", dest="N_FRAMES_PER_BURST", default=0, type=int)
|
||||||
|
parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=0, type=int)
|
||||||
|
parser.add_argument("--txmode", dest="FREEDV_TX_MODE", default=0, type=int)
|
||||||
|
parser.add_argument("--rxmode", dest="FREEDV_RX_MODE", default=0, type=int)
|
||||||
|
parser.add_argument("--audiooutput", dest="AUDIO_OUTPUT", default=0, type=int)
|
||||||
|
parser.add_argument("--audioinput", dest="AUDIO_INPUT", default=0, type=int)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
N_BURSTS = args.N_BURSTS
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000
|
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
|
||||||
|
|
||||||
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT
|
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT
|
||||||
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT
|
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT
|
||||||
|
@ -40,72 +39,77 @@ FREEDV_TX_MODE = args.FREEDV_TX_MODE
|
||||||
FREEDV_RX_MODE = args.FREEDV_RX_MODE
|
FREEDV_RX_MODE = args.FREEDV_RX_MODE
|
||||||
|
|
||||||
DEBUGGING_MODE = args.DEBUGGING_MODE
|
DEBUGGING_MODE = args.DEBUGGING_MODE
|
||||||
#-------------------------------------------- LOAD FREEDV
|
# -------------------------------------------- LOAD FREEDV
|
||||||
libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so"
|
libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so"
|
||||||
c_lib = ctypes.CDLL(libname)
|
c_lib = ctypes.CDLL(str(libname))
|
||||||
|
|
||||||
#--------------------------------------------CREATE PYAUDIO INSTANCE
|
# --------------------------------------------CREATE PYAUDIO INSTANCE
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
#--------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE
|
# --------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE
|
||||||
#AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate'])
|
# AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate'])
|
||||||
#AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate'])
|
# AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate'])
|
||||||
AUDIO_SAMPLE_RATE_TX = 8000
|
AUDIO_SAMPLE_RATE_TX = 8000
|
||||||
AUDIO_SAMPLE_RATE_RX = 8000
|
AUDIO_SAMPLE_RATE_RX = 8000
|
||||||
#--------------------------------------------OPEN AUDIO CHANNEL TX
|
# --------------------------------------------OPEN AUDIO CHANNEL TX
|
||||||
|
|
||||||
stream_tx = p.open(format=pyaudio.paInt16,
|
stream_tx = p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=AUDIO_SAMPLE_RATE_TX,
|
rate=AUDIO_SAMPLE_RATE_TX,
|
||||||
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples
|
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples
|
||||||
output=True,
|
output=True,
|
||||||
output_device_index=AUDIO_OUTPUT_DEVICE,
|
output_device_index=AUDIO_OUTPUT_DEVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
stream_rx = p.open(format=pyaudio.paInt16,
|
stream_rx = p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=AUDIO_SAMPLE_RATE_RX,
|
rate=AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
input_device_index=AUDIO_INPUT_DEVICE,
|
input_device_index=AUDIO_INPUT_DEVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def receive():
|
def receive():
|
||||||
|
|
||||||
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
|
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
|
||||||
freedv = c_lib.freedv_open(FREEDV_RX_MODE)
|
freedv = c_lib.freedv_open(FREEDV_RX_MODE)
|
||||||
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8)
|
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
payload_per_frame = bytes_per_frame -2
|
payload_per_frame = bytes_per_frame - 2
|
||||||
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv)
|
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv)
|
||||||
n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2
|
n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(
|
||||||
|
freedv
|
||||||
|
) # get n_tx_modem_samples which defines the size of the modulation object # --> *2
|
||||||
|
|
||||||
bytes_out = (ctypes.c_ubyte * bytes_per_frame) #bytes_per_frame
|
bytes_out = ctypes.c_ubyte * bytes_per_frame # bytes_per_frame
|
||||||
bytes_out = bytes_out() #get pointer from bytes_out
|
bytes_out = bytes_out() # get pointer from bytes_out
|
||||||
|
|
||||||
total_n_bytes = 0
|
total_n_bytes = 0
|
||||||
rx_total_frames = 0
|
rx_total_frames = 0
|
||||||
rx_frames = 0
|
rx_frames = 0
|
||||||
rx_bursts = 0
|
rx_bursts = 0
|
||||||
receive = True
|
receive = True
|
||||||
while receive == True:
|
while receive:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
nin = c_lib.freedv_nin(freedv)
|
nin = c_lib.freedv_nin(freedv)
|
||||||
nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE))
|
nin_converted = int(nin * (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE))
|
||||||
if DEBUGGING_MODE == True:
|
if DEBUGGING_MODE:
|
||||||
print("-----------------------------")
|
print("-----------------------------")
|
||||||
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
|
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
|
||||||
|
|
||||||
data_in = stream_rx.read(nin_converted, exception_on_overflow = False)
|
data_in = stream_rx.read(nin_converted, exception_on_overflow=False)
|
||||||
data_in = data_in.rstrip(b'\x00')
|
data_in = data_in.rstrip(b"\x00")
|
||||||
|
|
||||||
c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary
|
c_lib.freedv_rawdatarx.argtype = [
|
||||||
|
ctypes.POINTER(ctypes.c_ubyte),
|
||||||
|
bytes_out,
|
||||||
|
data_in,
|
||||||
|
] # check if really neccessary
|
||||||
nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio
|
nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio
|
||||||
total_n_bytes = total_n_bytes + nbytes
|
total_n_bytes = total_n_bytes + nbytes
|
||||||
if DEBUGGING_MODE == True:
|
if DEBUGGING_MODE:
|
||||||
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
|
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
|
||||||
|
|
||||||
if nbytes == bytes_per_frame:
|
if nbytes == bytes_per_frame:
|
||||||
|
@ -115,79 +119,106 @@ def receive():
|
||||||
if rx_frames == N_FRAMES_PER_BURST:
|
if rx_frames == N_FRAMES_PER_BURST:
|
||||||
rx_frames = 0
|
rx_frames = 0
|
||||||
rx_bursts = rx_bursts + 1
|
rx_bursts = rx_bursts + 1
|
||||||
c_lib.freedv_set_sync(freedv,0)
|
c_lib.freedv_set_sync(freedv, 0)
|
||||||
|
|
||||||
|
|
||||||
burst = bytes_out[0]
|
burst = bytes_out[0]
|
||||||
n_total_burst = bytes_out[1]
|
n_total_burst = bytes_out[1]
|
||||||
frame = bytes_out[2]
|
frame = bytes_out[2]
|
||||||
n_total_frame = bytes_out[3]
|
n_total_frame = bytes_out[3]
|
||||||
|
|
||||||
|
print(
|
||||||
print("RX | PONG | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "]")
|
"RX | PONG | BURST ["
|
||||||
|
+ str(burst)
|
||||||
|
+ "/"
|
||||||
|
+ str(n_total_burst)
|
||||||
|
+ "] FRAME ["
|
||||||
|
+ str(frame)
|
||||||
|
+ "/"
|
||||||
|
+ str(n_total_frame)
|
||||||
|
+ "]"
|
||||||
|
)
|
||||||
print("-----------------------------------------------------------------")
|
print("-----------------------------------------------------------------")
|
||||||
c_lib.freedv_set_sync(freedv,0)
|
c_lib.freedv_set_sync(freedv, 0)
|
||||||
|
|
||||||
|
|
||||||
if rx_bursts == N_BURSTS:
|
if rx_bursts == N_BURSTS:
|
||||||
receive = False
|
receive = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD")
|
RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD")
|
||||||
RECEIVE.start()
|
RECEIVE.start()
|
||||||
|
|
||||||
|
|
||||||
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
|
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
|
||||||
freedv = c_lib.freedv_open(FREEDV_TX_MODE)
|
freedv = c_lib.freedv_open(FREEDV_TX_MODE)
|
||||||
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8)
|
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
payload_per_frame = bytes_per_frame -2
|
payload_per_frame = bytes_per_frame - 2
|
||||||
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv)
|
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv)
|
||||||
n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) #get n_tx_modem_samples which defines the size of the modulation object # --> *2
|
n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(
|
||||||
|
freedv
|
||||||
|
) # get n_tx_modem_samples which defines the size of the modulation object # --> *2
|
||||||
|
|
||||||
mod_out = ctypes.c_short * n_tx_modem_samples
|
mod_out = ctypes.c_short * n_tx_modem_samples
|
||||||
mod_out = mod_out()
|
mod_out = mod_out()
|
||||||
mod_out_preamble = ctypes.c_short * (1760*2) #1760 for mode 10,11,12 #4000 for mode 9
|
mod_out_preamble = ctypes.c_short * (
|
||||||
|
1760 * 2
|
||||||
|
) # 1760 for mode 10,11,12 #4000 for mode 9
|
||||||
mod_out_preamble = mod_out_preamble()
|
mod_out_preamble = mod_out_preamble()
|
||||||
|
|
||||||
|
|
||||||
|
print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST))
|
||||||
print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST) )
|
|
||||||
print("-----------------------------------------------------------------")
|
print("-----------------------------------------------------------------")
|
||||||
|
|
||||||
for i in range(0,N_BURSTS):
|
for i in range(N_BURSTS):
|
||||||
|
|
||||||
c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble);
|
c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
|
|
||||||
txbuffer = bytearray()
|
txbuffer = bytearray()
|
||||||
txbuffer += bytes(mod_out_preamble)
|
txbuffer += bytes(mod_out_preamble)
|
||||||
|
|
||||||
for n in range(0,N_FRAMES_PER_BURST):
|
for n in range(N_FRAMES_PER_BURST):
|
||||||
|
|
||||||
data_out = bytearray()
|
data_out = bytearray()
|
||||||
data_out += bytes([i+1])
|
data_out += bytes([i + 1])
|
||||||
data_out += bytes([N_BURSTS])
|
data_out += bytes([N_BURSTS])
|
||||||
data_out += bytes([n+1])
|
data_out += bytes([n + 1])
|
||||||
data_out += bytes([N_FRAMES_PER_BURST])
|
data_out += bytes([N_FRAMES_PER_BURST])
|
||||||
|
|
||||||
buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3)
|
buffer = bytearray(
|
||||||
buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send
|
payload_per_frame
|
||||||
|
) # use this if CRC16 checksum is required ( DATA1-3)
|
||||||
|
buffer[
|
||||||
|
: len(data_out)
|
||||||
|
] = data_out # set buffersize to length of data which will be send
|
||||||
|
|
||||||
crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16
|
crc = ctypes.c_ushort(
|
||||||
crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string
|
c_lib.freedv_gen_crc16(bytes(buffer), payload_per_frame)
|
||||||
|
) # generate CRC16
|
||||||
|
crc = crc.value.to_bytes(2, byteorder="big") # convert crc to 2 byte hex string
|
||||||
buffer += crc # append crc16 to buffer
|
buffer += crc # append crc16 to buffer
|
||||||
|
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer
|
c_lib.freedv_rawdatatx(
|
||||||
|
freedv, mod_out, data
|
||||||
|
) # modulate DATA and safe it into mod_out pointer
|
||||||
|
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
|
|
||||||
print("TX | PING | BURST [" + str(i+1) + "/" + str(N_BURSTS) + "] FRAME [" + str(n+1) + "/" + str(N_FRAMES_PER_BURST) + "]")
|
print(
|
||||||
|
"TX | PING | BURST ["
|
||||||
|
+ str(i + 1)
|
||||||
|
+ "/"
|
||||||
|
+ str(N_BURSTS)
|
||||||
|
+ "] FRAME ["
|
||||||
|
+ str(n + 1)
|
||||||
|
+ "/"
|
||||||
|
+ str(N_FRAMES_PER_BURST)
|
||||||
|
+ "]"
|
||||||
|
)
|
||||||
stream_tx.write(bytes(txbuffer))
|
stream_tx.write(bytes(txbuffer))
|
||||||
ACK_TIMEOUT = time.time() + 3
|
ACK_TIMEOUT = time.time() + 3
|
||||||
txbuffer = bytearray()
|
txbuffer = bytearray()
|
||||||
|
|
||||||
#time.sleep(DELAY_BETWEEN_BURSTS)
|
# time.sleep(DELAY_BETWEEN_BURSTS)
|
||||||
|
|
||||||
# WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME
|
# WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME
|
||||||
while ACK_TIMEOUT >= time.time():
|
while ACK_TIMEOUT >= time.time():
|
||||||
|
|
|
@ -27,7 +27,7 @@ parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int)
|
||||||
parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int)
|
parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int)
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
N_BURSTS = args.N_BURSTS
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Unit test for FreeDV API resampler functions, from
|
|
||||||
# codec2/unittest/t48_8_short.c - generate a sine wave at 8 KHz,
|
|
||||||
# upsample to 48 kHz, add an interferer, then downsample back to 8 kHz
|
|
||||||
#
|
|
||||||
# You can listen to the output files with:
|
|
||||||
#
|
|
||||||
# aplay -f S16_LE in8.raw
|
|
||||||
# aplay -r 48000 -f S16_LE out48.raw
|
|
||||||
# aplay -f S16_LE out8.raw
|
|
||||||
#
|
|
||||||
# They should sound like clean sine waves
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
sys.path.insert(0,'..')
|
|
||||||
from tnc import codec2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# dig some constants out
|
|
||||||
FDMDV_OS_48 = codec2.api.FDMDV_OS_48
|
|
||||||
FDMDV_OS_TAPS_48K = codec2.api.FDMDV_OS_TAPS_48K
|
|
||||||
FDMDV_OS_TAPS_48_8K = codec2.api.FDMDV_OS_TAPS_48_8K
|
|
||||||
|
|
||||||
N8 = int(180) # processing buffer size at 8 kHz
|
|
||||||
N48 = int(N8*FDMDV_OS_48) # processing buffer size at 48 kHz
|
|
||||||
MEM8 = FDMDV_OS_TAPS_48_8K # 8kHz signal filter memory
|
|
||||||
MEM48 = FDMDV_OS_TAPS_48K # 48kHz signal filter memory
|
|
||||||
FRAMES = int(50) # number of frames to test
|
|
||||||
FS8 = 8000
|
|
||||||
FS48 = 48000
|
|
||||||
AMP = 16000 # sine wave amplitude
|
|
||||||
FTEST8 = 800 # input test frequency at FS=8kHz
|
|
||||||
FINTER48 = 10000 # interferer frequency at FS=48kHz
|
|
||||||
|
|
||||||
# Due to the design of these resamplers, the processing buffer (at 8kHz)
|
|
||||||
# must be an integer multiple of oversampling ratio
|
|
||||||
assert N8 % FDMDV_OS_48 == 0
|
|
||||||
|
|
||||||
# time indexes, we advance every frame
|
|
||||||
t = 0
|
|
||||||
t1 = 0
|
|
||||||
|
|
||||||
# output files to listen to/evaluate result
|
|
||||||
fin8 = open("in8.raw", mode='wb')
|
|
||||||
f48 = open("out48.raw", mode='wb')
|
|
||||||
fout8 = open("out8.raw", mode='wb')
|
|
||||||
|
|
||||||
resampler = codec2.resampler()
|
|
||||||
|
|
||||||
for f in range(FRAMES):
|
|
||||||
|
|
||||||
sine_in8k = (AMP*np.cos(2*np.pi*np.arange(t,t+N8)*FTEST8/FS8)).astype(np.int16)
|
|
||||||
t += N8
|
|
||||||
sine_in8k.tofile(fin8)
|
|
||||||
|
|
||||||
sine_out48k = resampler.resample8_to_48(sine_in8k)
|
|
||||||
sine_out48k.tofile(f48)
|
|
||||||
|
|
||||||
# add interfering sine wave (down sampling filter should remove)
|
|
||||||
sine_in48k = (sine_out48k + (AMP/2)*np.cos(2*np.pi*np.arange(t1,t1+N48)*FINTER48/FS48)).astype(np.int16)
|
|
||||||
t1 += N48
|
|
||||||
|
|
||||||
sine_out8k = resampler.resample48_to_8(sine_in48k)
|
|
||||||
sine_out8k.tofile(fout8)
|
|
||||||
|
|
||||||
fin8.close()
|
|
||||||
f48.close()
|
|
||||||
fout8.close()
|
|
||||||
|
|
||||||
# Automated test evaluation --------------------------------------------
|
|
||||||
|
|
||||||
# The input and output signals will not be time aligned due to the filter
|
|
||||||
# delays, so compare the magnitude spectrum
|
|
||||||
|
|
||||||
in8k = np.fromfile("in8.raw", dtype=np.int16)
|
|
||||||
out8k = np.fromfile("out8.raw", dtype=np.int16)
|
|
||||||
assert len(in8k) == len(out8k)
|
|
||||||
|
|
||||||
n = len(in8k)
|
|
||||||
|
|
||||||
h = np.hanning(len(in8k))
|
|
||||||
S1 = np.abs(np.fft.fft(in8k * h))
|
|
||||||
S2 = np.abs(np.fft.fft(out8k * h))
|
|
||||||
|
|
||||||
error = S1-S2
|
|
||||||
error_energy = np.dot(error,error)
|
|
||||||
ratio = error_energy/np.dot(S1,S1)
|
|
||||||
ratio_dB = 10*np.log10(ratio);
|
|
||||||
print("ratio_dB: %4.2f" % (ratio_dB));
|
|
||||||
threshdB = -40
|
|
||||||
if ratio_dB < threshdB:
|
|
||||||
print("PASS")
|
|
||||||
else:
|
|
||||||
print("FAIL")
|
|
|
@ -7,36 +7,64 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0,'..')
|
|
||||||
sys.path.insert(0,'../tnc')
|
|
||||||
import data_handler
|
|
||||||
import argparse
|
|
||||||
import codec2
|
import codec2
|
||||||
|
import data_handler
|
||||||
import modem
|
import modem
|
||||||
|
import pytest
|
||||||
parser = argparse.ArgumentParser(description='ARQ TEST')
|
import static
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}'
|
bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}'
|
||||||
|
|
||||||
# start data handler
|
|
||||||
data_handler.DATA()
|
|
||||||
|
|
||||||
# start modem
|
@pytest.mark.parametrize("freedv_mode", ["datac0", "datac1", "datac3"])
|
||||||
modem = modem.RF()
|
@pytest.mark.parametrize("n_frames_per_burst", [1, 2, 3])
|
||||||
|
def test_highsnr_arq_short(freedv_mode: str, n_frames_per_burst: int):
|
||||||
|
t_mode = t_repeats = t_repeat_delay = 0
|
||||||
|
t_frames = []
|
||||||
|
|
||||||
mode = codec2.freedv_get_mode(args.FREEDV_MODE)
|
def t_tx_dummy(mode, repeats, repeat_delay, frames):
|
||||||
print(mode)
|
"""Replacement function for transmit"""
|
||||||
n_frames_per_burst = args.N_FRAMES_PER_BURST
|
print(f"t_tx_dummy: In transmit({mode}, {repeats}, {repeat_delay}, {frames})")
|
||||||
|
nonlocal t_mode, t_repeats, t_repeat_delay, t_frames
|
||||||
|
t_mode = mode
|
||||||
|
t_repeats = repeats
|
||||||
|
t_repeat_delay = repeat_delay
|
||||||
|
t_frames = frames[:]
|
||||||
|
static.TRANSMITTING = False
|
||||||
|
|
||||||
# enable testmode
|
# Enable testmode
|
||||||
data_handler.TESTMODE = True
|
modem.TESTMODE = True
|
||||||
|
# Set some inner variables so the modules don't throw exceptions.
|
||||||
|
modem.RXCHANNEL = "/tmp/rxpipe"
|
||||||
|
modem.TXCHANNEL = "/tmp/txpipe"
|
||||||
|
data_handler.TESTMODE = True
|
||||||
|
static.HAMLIB_RADIOCONTROL = "disabled"
|
||||||
|
|
||||||
# add command to data qeue
|
# start data handler
|
||||||
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_FILE', bytes_out, mode, n_frames_per_burst])
|
data_handler.DATA()
|
||||||
|
|
||||||
|
# start modem
|
||||||
|
t_modem = modem.RF()
|
||||||
|
|
||||||
|
# Replace transmit routine with our own, an effective No-Op.
|
||||||
|
t_modem.transmit = t_tx_dummy
|
||||||
|
|
||||||
|
mode = codec2.freedv_get_mode_value_by_name(freedv_mode)
|
||||||
|
print(mode)
|
||||||
|
|
||||||
|
# add command to data qeue
|
||||||
|
data_handler.DATA_QUEUE_TRANSMIT.put(
|
||||||
|
["ARQ_FILE", bytes_out, mode, n_frames_per_burst]
|
||||||
|
)
|
||||||
|
|
||||||
|
# This test isn't complete yet, or is obsolete.
|
||||||
|
assert False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
||||||
|
|
|
@ -3,13 +3,16 @@
|
||||||
#
|
#
|
||||||
# tests audio buffer thread safety
|
# tests audio buffer thread safety
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0,'..')
|
|
||||||
from tnc import codec2
|
|
||||||
import threading
|
import threading
|
||||||
import numpy as np
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
import codec2
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
BUFFER_SZ = 1024
|
BUFFER_SZ = 1024
|
||||||
N_MAX = 100 # write a repeating sequence of 0..N_MAX-1
|
N_MAX = 100 # write a repeating sequence of 0..N_MAX-1
|
||||||
WRITE_SZ = 10 # different read and write sized buffers
|
WRITE_SZ = 10 # different read and write sized buffers
|
||||||
|
@ -19,34 +22,44 @@ NTESTS = 10000
|
||||||
running = True
|
running = True
|
||||||
audio_buffer = codec2.audio_buffer(BUFFER_SZ)
|
audio_buffer = codec2.audio_buffer(BUFFER_SZ)
|
||||||
|
|
||||||
n_write = int(0)
|
n_write = 0
|
||||||
n_read = int(0)
|
|
||||||
|
|
||||||
def writer():
|
|
||||||
|
def t_writer():
|
||||||
|
"""
|
||||||
|
Subprocess to handle writes to the NumPY audio "device."
|
||||||
|
"""
|
||||||
global n_write
|
global n_write
|
||||||
print("writer starting")
|
print("writer starting")
|
||||||
n = int(0)
|
n = 0
|
||||||
buf = np.zeros(WRITE_SZ, dtype=np.int16)
|
buf = np.zeros(WRITE_SZ, dtype=np.int16)
|
||||||
while running:
|
while running:
|
||||||
nfree = audio_buffer.size - audio_buffer.nbuffer
|
nfree = audio_buffer.size - audio_buffer.nbuffer
|
||||||
if nfree >= WRITE_SZ:
|
if nfree >= WRITE_SZ:
|
||||||
for i in range(0,WRITE_SZ):
|
for index in range(WRITE_SZ):
|
||||||
buf[i] = n;
|
buf[index] = n
|
||||||
n += 1
|
n += 1
|
||||||
if n == N_MAX:
|
if n == N_MAX:
|
||||||
n = 0
|
n = 0
|
||||||
n_write += 1
|
n_write += 1
|
||||||
audio_buffer.push(buf)
|
audio_buffer.push(buf)
|
||||||
|
|
||||||
x = threading.Thread(target=writer)
|
|
||||||
x.start()
|
|
||||||
|
|
||||||
n_out = int(0)
|
def test_audiobuffer():
|
||||||
errors = int(0)
|
"""
|
||||||
for tests in range(1,NTESTS):
|
Test for the audiobuffer
|
||||||
|
"""
|
||||||
|
global running
|
||||||
|
|
||||||
|
# Start the writer in a new thread.
|
||||||
|
writer_thread = threading.Thread(target=t_writer)
|
||||||
|
writer_thread.start()
|
||||||
|
|
||||||
|
n_out = n_read = errors = 0
|
||||||
|
for _ in range(NTESTS):
|
||||||
while audio_buffer.nbuffer < READ_SZ:
|
while audio_buffer.nbuffer < READ_SZ:
|
||||||
sleep(0.001)
|
sleep(0.001)
|
||||||
for i in range(0,READ_SZ):
|
for i in range(READ_SZ):
|
||||||
if audio_buffer.buffer[i] != n_out:
|
if audio_buffer.buffer[i] != n_out:
|
||||||
errors += 1
|
errors += 1
|
||||||
n_out += 1
|
n_out += 1
|
||||||
|
@ -55,5 +68,21 @@ for tests in range(1,NTESTS):
|
||||||
n_read += 1
|
n_read += 1
|
||||||
audio_buffer.pop(READ_SZ)
|
audio_buffer.pop(READ_SZ)
|
||||||
|
|
||||||
running = False
|
print(f"n_write: {n_write} n_read: {n_read} errors: {errors} ")
|
||||||
print("n_write: %d n_read: %d errors: %d " % (n_write, n_read, errors))
|
|
||||||
|
# Indirectly stop the thread
|
||||||
|
running = False
|
||||||
|
sleep(0.1)
|
||||||
|
|
||||||
|
assert not writer_thread.is_alive()
|
||||||
|
assert n_write - n_read < BUFFER_SZ
|
||||||
|
assert errors == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
||||||
|
|
|
@ -6,41 +6,54 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, help="audio device number to use")
|
parser.add_argument(
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
"--audiodev",
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.N_BURSTS = args.N_BURSTS
|
self.N_BURSTS = args.N_BURSTS
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
@ -49,14 +62,15 @@ class Test():
|
||||||
self.TIMEOUT = args.TIMEOUT
|
self.TIMEOUT = args.TIMEOUT
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0
|
# v-- consider increasing if you get nread_exceptions > 0
|
||||||
|
self.AUDIO_FRAMES_PER_BUFFER = 2400 * 2
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_RX = 48000
|
self.AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_INPUT_DEVICE != -1:
|
if self.AUDIO_INPUT_DEVICE != -1:
|
||||||
|
@ -64,50 +78,73 @@ class Test():
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_INPUT_DEVICE == -2:
|
if self.AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
for dev in range(0,self.p.get_device_count()):
|
for dev in range(self.p.get_device_count()):
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
|
||||||
loopback_list.append(dev)
|
loopback_list.append(dev)
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
# 0 = RX 1 = TX
|
||||||
|
self.AUDIO_INPUT_DEVICE = loopback_list[0]
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
|
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
||||||
|
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_rx = self.p.open(format=pyaudio.paInt16,
|
self.stream_rx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_RX,
|
rate=self.AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
output=False,
|
output=False,
|
||||||
input_device_index=self.AUDIO_INPUT_DEVICE,
|
input_device_index=self.AUDIO_INPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
print("test_callback_multimode_rx: Not written for STDIN usage.")
|
||||||
|
print("Exiting.")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p)
|
self.datac0_freedv = ctypes.cast(
|
||||||
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv)/8)
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
|
||||||
self.datac0_bytes_out = create_string_buffer(self.datac0_bytes_per_frame)
|
)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv,self.N_FRAMES_PER_BURST)
|
self.datac0_bytes_per_frame = int(
|
||||||
self.datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
|
||||||
|
)
|
||||||
self.datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p)
|
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
|
||||||
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv)/8)
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
self.datac1_bytes_out = create_string_buffer(self.datac1_bytes_per_frame)
|
self.datac0_freedv, self.N_FRAMES_PER_BURST
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv,self.N_FRAMES_PER_BURST)
|
)
|
||||||
self.datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
self.datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
|
|
||||||
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv)/8)
|
|
||||||
self.datac3_bytes_out = create_string_buffer(self.datac3_bytes_per_frame)
|
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv,self.N_FRAMES_PER_BURST)
|
|
||||||
self.datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
|
||||||
|
|
||||||
|
self.datac1_freedv = ctypes.cast(
|
||||||
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
|
||||||
|
)
|
||||||
|
self.datac1_bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
|
||||||
|
)
|
||||||
|
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
|
||||||
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
|
self.datac1_freedv, self.N_FRAMES_PER_BURST
|
||||||
|
)
|
||||||
|
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
|
self.datac3_freedv = ctypes.cast(
|
||||||
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
|
||||||
|
)
|
||||||
|
self.datac3_bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
|
||||||
|
)
|
||||||
|
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
|
||||||
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
|
self.datac3_freedv, self.N_FRAMES_PER_BURST
|
||||||
|
)
|
||||||
|
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
# SET COUNTERS
|
# SET COUNTERS
|
||||||
self.rx_total_frames_datac0 = 0
|
self.rx_total_frames_datac0 = 0
|
||||||
|
@ -131,19 +168,18 @@ class Test():
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.frx = open("rx48_callback_multimode.raw", mode='wb')
|
self.frx = open("rx48_callback_multimode.raw", mode="wb")
|
||||||
|
|
||||||
|
|
||||||
# initial nin values
|
# initial nin values
|
||||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||||
|
|
||||||
|
self.LOGGER_THREAD = threading.Thread(
|
||||||
self.LOGGER_THREAD = threading.Thread(target=self.print_stats, name="LOGGER_THREAD")
|
target=self.print_stats, name="LOGGER_THREAD"
|
||||||
|
)
|
||||||
self.LOGGER_THREAD.start()
|
self.LOGGER_THREAD.start()
|
||||||
|
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
x.tofile(self.frx)
|
x.tofile(self.frx)
|
||||||
|
@ -153,10 +189,13 @@ class Test():
|
||||||
self.datac1_buffer.push(x)
|
self.datac1_buffer.push(x)
|
||||||
self.datac3_buffer.push(x)
|
self.datac3_buffer.push(x)
|
||||||
|
|
||||||
|
|
||||||
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac0_freedv,
|
||||||
|
self.datac0_bytes_out,
|
||||||
|
self.datac0_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac0_buffer.pop(self.datac0_nin)
|
self.datac0_buffer.pop(self.datac0_nin)
|
||||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||||
if nbytes == self.datac0_bytes_per_frame:
|
if nbytes == self.datac0_bytes_per_frame:
|
||||||
|
@ -167,10 +206,13 @@ class Test():
|
||||||
self.rx_frames_datac0 = 0
|
self.rx_frames_datac0 = 0
|
||||||
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
|
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
|
||||||
|
|
||||||
|
|
||||||
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac1_freedv,
|
||||||
|
self.datac1_bytes_out,
|
||||||
|
self.datac1_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac1_buffer.pop(self.datac1_nin)
|
self.datac1_buffer.pop(self.datac1_nin)
|
||||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||||
if nbytes == self.datac1_bytes_per_frame:
|
if nbytes == self.datac1_bytes_per_frame:
|
||||||
|
@ -181,10 +223,13 @@ class Test():
|
||||||
self.rx_frames_datac1 = 0
|
self.rx_frames_datac1 = 0
|
||||||
self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1
|
self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1
|
||||||
|
|
||||||
|
|
||||||
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac3_freedv,
|
||||||
|
self.datac3_bytes_out,
|
||||||
|
self.datac3_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac3_buffer.pop(self.datac3_nin)
|
self.datac3_buffer.pop(self.datac3_nin)
|
||||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||||
if nbytes == self.datac3_bytes_per_frame:
|
if nbytes == self.datac3_bytes_per_frame:
|
||||||
|
@ -195,8 +240,9 @@ class Test():
|
||||||
self.rx_frames_datac3 = 0
|
self.rx_frames_datac3 = 0
|
||||||
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
|
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
|
||||||
|
|
||||||
|
if (
|
||||||
if (self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3) == self.N_BURSTS:
|
self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3
|
||||||
|
) == self.N_BURSTS:
|
||||||
self.receive = False
|
self.receive = False
|
||||||
|
|
||||||
return (None, pyaudio.paContinue)
|
return (None, pyaudio.paContinue)
|
||||||
|
@ -205,19 +251,39 @@ class Test():
|
||||||
while self.receive:
|
while self.receive:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
if self.DEBUGGING_MODE:
|
if self.DEBUGGING_MODE:
|
||||||
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv)
|
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(
|
||||||
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac0_rxstatus]
|
self.datac0_freedv
|
||||||
|
)
|
||||||
|
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
|
self.datac0_rxstatus
|
||||||
|
]
|
||||||
|
|
||||||
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv)
|
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(
|
||||||
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac1_rxstatus]
|
self.datac1_freedv
|
||||||
|
)
|
||||||
|
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
|
self.datac1_rxstatus
|
||||||
|
]
|
||||||
|
|
||||||
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv)
|
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(
|
||||||
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac3_rxstatus]
|
self.datac3_freedv
|
||||||
|
)
|
||||||
print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \
|
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
(self.datac0_nin, self.datac0_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, self.datac3_rxstatus),
|
self.datac3_rxstatus
|
||||||
file=sys.stderr)
|
]
|
||||||
|
|
||||||
|
print(
|
||||||
|
"NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s"
|
||||||
|
% (
|
||||||
|
self.datac0_nin,
|
||||||
|
self.datac0_rxstatus,
|
||||||
|
self.datac1_nin,
|
||||||
|
self.datac1_rxstatus,
|
||||||
|
self.datac3_nin,
|
||||||
|
self.datac3_rxstatus,
|
||||||
|
),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
def run_audio(self):
|
def run_audio(self):
|
||||||
try:
|
try:
|
||||||
|
@ -226,7 +292,6 @@ class Test():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"pyAudio error: {e}", file=sys.stderr)
|
print(f"pyAudio error: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
while self.receive and time.time() < self.timeout:
|
while self.receive and time.time() < self.timeout:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
@ -235,10 +300,16 @@ class Test():
|
||||||
self.receive = False
|
self.receive = False
|
||||||
|
|
||||||
if self.nread_exceptions:
|
if self.nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
self.nread_exceptions, file=sys.stderr)
|
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
|
||||||
|
% self.nread_exceptions,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
print(f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", file=sys.stderr)
|
print(
|
||||||
|
f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
self.frx.close()
|
self.frx.close()
|
||||||
|
|
||||||
# cloese pyaudio instance
|
# cloese pyaudio instance
|
||||||
|
|
|
@ -6,41 +6,52 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, help="audio device number to use")
|
parser.add_argument(
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
"--audiodev",
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.N_BURSTS = args.N_BURSTS
|
self.N_BURSTS = args.N_BURSTS
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
@ -49,63 +60,91 @@ class Test():
|
||||||
self.TIMEOUT = args.TIMEOUT
|
self.TIMEOUT = args.TIMEOUT
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0
|
self.AUDIO_FRAMES_PER_BUFFER = (
|
||||||
|
2400 * 2
|
||||||
|
) # <- consider increasing if you get nread_exceptions > 0
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_RX = 48000
|
self.AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_INPUT_DEVICE != -1:
|
if self.AUDIO_INPUT_DEVICE != -1:
|
||||||
self.p = pyaudio.PyAudio()
|
self.p = pyaudio.PyAudio()
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_INPUT_DEVICE == -2:
|
if self.AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = [
|
||||||
for dev in range(0,self.p.get_device_count()):
|
dev
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
for dev in range(self.p.get_device_count())
|
||||||
loopback_list.append(dev)
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]
|
||||||
|
]
|
||||||
|
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
|
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
||||||
|
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_rx = self.p.open(format=pyaudio.paInt16,
|
self.stream_rx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_RX,
|
rate=self.AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
output=False,
|
output=False,
|
||||||
input_device_index=self.AUDIO_INPUT_DEVICE,
|
input_device_index=self.AUDIO_INPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
print("test_callback_multimode_rx_outside: Not written for STDIN usage.")
|
||||||
|
print("Exiting.")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p)
|
self.datac0_freedv = ctypes.cast(
|
||||||
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv)/8)
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
|
||||||
self.datac0_bytes_out = create_string_buffer(self.datac0_bytes_per_frame)
|
)
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv,self.N_FRAMES_PER_BURST)
|
self.datac0_bytes_per_frame = int(
|
||||||
self.datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
|
||||||
|
)
|
||||||
self.datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p)
|
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
|
||||||
self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv)/8)
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
self.datac1_bytes_out = create_string_buffer(self.datac1_bytes_per_frame)
|
self.datac0_freedv, self.N_FRAMES_PER_BURST
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv,self.N_FRAMES_PER_BURST)
|
)
|
||||||
self.datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
self.datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
|
|
||||||
self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv)/8)
|
|
||||||
self.datac3_bytes_out = create_string_buffer(self.datac3_bytes_per_frame)
|
|
||||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv,self.N_FRAMES_PER_BURST)
|
|
||||||
self.datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER)
|
|
||||||
|
|
||||||
|
self.datac1_freedv = ctypes.cast(
|
||||||
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
|
||||||
|
)
|
||||||
|
self.datac1_bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
|
||||||
|
)
|
||||||
|
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
|
||||||
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
|
self.datac1_freedv, self.N_FRAMES_PER_BURST
|
||||||
|
)
|
||||||
|
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
|
self.datac3_freedv = ctypes.cast(
|
||||||
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
|
||||||
|
)
|
||||||
|
self.datac3_bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
|
||||||
|
)
|
||||||
|
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
|
||||||
|
codec2.api.freedv_set_frames_per_burst(
|
||||||
|
self.datac3_freedv, self.N_FRAMES_PER_BURST
|
||||||
|
)
|
||||||
|
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER)
|
||||||
|
|
||||||
# SET COUNTERS
|
# SET COUNTERS
|
||||||
self.rx_total_frames_datac0 = 0
|
self.rx_total_frames_datac0 = 0
|
||||||
|
@ -129,15 +168,13 @@ class Test():
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.frx = open("rx48_callback_multimode.raw", mode='wb')
|
self.frx = open("rx48_callback_multimode.raw", mode="wb")
|
||||||
|
|
||||||
|
|
||||||
# initial nin values
|
# initial nin values
|
||||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||||
|
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
|
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
|
@ -148,26 +185,37 @@ class Test():
|
||||||
self.datac1_buffer.push(x)
|
self.datac1_buffer.push(x)
|
||||||
self.datac3_buffer.push(x)
|
self.datac3_buffer.push(x)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (None, pyaudio.paContinue)
|
return (None, pyaudio.paContinue)
|
||||||
|
|
||||||
def print_stats(self):
|
def print_stats(self):
|
||||||
if self.DEBUGGING_MODE:
|
if self.DEBUGGING_MODE:
|
||||||
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv)
|
self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv)
|
||||||
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac0_rxstatus]
|
self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
|
self.datac0_rxstatus
|
||||||
|
]
|
||||||
|
|
||||||
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv)
|
self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv)
|
||||||
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac1_rxstatus]
|
self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
|
self.datac1_rxstatus
|
||||||
|
]
|
||||||
|
|
||||||
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv)
|
self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv)
|
||||||
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[self.datac3_rxstatus]
|
self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[
|
||||||
|
self.datac3_rxstatus
|
||||||
print("NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % \
|
]
|
||||||
(self.datac0_nin, self.datac0_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, self.datac3_rxstatus),
|
|
||||||
file=sys.stderr)
|
|
||||||
|
|
||||||
|
print(
|
||||||
|
"NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s"
|
||||||
|
% (
|
||||||
|
self.datac0_nin,
|
||||||
|
self.datac0_rxstatus,
|
||||||
|
self.datac1_nin,
|
||||||
|
self.datac1_rxstatus,
|
||||||
|
self.datac3_nin,
|
||||||
|
self.datac3_rxstatus,
|
||||||
|
),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
def run_audio(self):
|
def run_audio(self):
|
||||||
try:
|
try:
|
||||||
|
@ -176,11 +224,14 @@ class Test():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"pyAudio error: {e}", file=sys.stderr)
|
print(f"pyAudio error: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
while self.receive and time.time() < self.timeout:
|
while self.receive and time.time() < self.timeout:
|
||||||
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
while self.datac0_buffer.nbuffer >= self.datac0_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac0_freedv,
|
||||||
|
self.datac0_bytes_out,
|
||||||
|
self.datac0_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac0_buffer.pop(self.datac0_nin)
|
self.datac0_buffer.pop(self.datac0_nin)
|
||||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||||
if nbytes == self.datac0_bytes_per_frame:
|
if nbytes == self.datac0_bytes_per_frame:
|
||||||
|
@ -192,10 +243,13 @@ class Test():
|
||||||
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
|
self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1
|
||||||
self.print_stats()
|
self.print_stats()
|
||||||
|
|
||||||
|
|
||||||
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
while self.datac1_buffer.nbuffer >= self.datac1_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac1_freedv,
|
||||||
|
self.datac1_bytes_out,
|
||||||
|
self.datac1_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac1_buffer.pop(self.datac1_nin)
|
self.datac1_buffer.pop(self.datac1_nin)
|
||||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||||
if nbytes == self.datac1_bytes_per_frame:
|
if nbytes == self.datac1_bytes_per_frame:
|
||||||
|
@ -209,7 +263,11 @@ class Test():
|
||||||
|
|
||||||
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
while self.datac3_buffer.nbuffer >= self.datac3_nin:
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.datac3_freedv,
|
||||||
|
self.datac3_bytes_out,
|
||||||
|
self.datac3_buffer.buffer.ctypes,
|
||||||
|
)
|
||||||
self.datac3_buffer.pop(self.datac3_nin)
|
self.datac3_buffer.pop(self.datac3_nin)
|
||||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||||
if nbytes == self.datac3_bytes_per_frame:
|
if nbytes == self.datac3_bytes_per_frame:
|
||||||
|
@ -221,18 +279,27 @@ class Test():
|
||||||
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
|
self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1
|
||||||
self.print_stats()
|
self.print_stats()
|
||||||
|
|
||||||
if (self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3) == self.N_BURSTS:
|
if (
|
||||||
|
self.rx_bursts_datac0
|
||||||
|
and self.rx_bursts_datac1
|
||||||
|
and self.rx_bursts_datac3
|
||||||
|
) == self.N_BURSTS:
|
||||||
self.receive = False
|
self.receive = False
|
||||||
|
|
||||||
|
|
||||||
if time.time() >= self.timeout:
|
if time.time() >= self.timeout:
|
||||||
print("TIMEOUT REACHED")
|
print("TIMEOUT REACHED")
|
||||||
|
|
||||||
if self.nread_exceptions:
|
if self.nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
self.nread_exceptions, file=sys.stderr)
|
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
|
||||||
|
% self.nread_exceptions,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
print(f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", file=sys.stderr)
|
print(
|
||||||
|
f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
self.frx.close()
|
self.frx.close()
|
||||||
|
|
||||||
# cloese pyaudio instance
|
# cloese pyaudio instance
|
||||||
|
|
|
@ -6,106 +6,130 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
import queue
|
import queue
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use")
|
parser.add_argument(
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
"--audiodev",
|
||||||
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="generate testframes")
|
dest="AUDIO_OUTPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio output device number to use",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--testframes",
|
||||||
|
dest="TESTFRAMES",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="generate testframes",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.dataqueue = queue.Queue()
|
self.dataqueue = queue.Queue()
|
||||||
self.N_BURSTS = args.N_BURSTS
|
self.N_BURSTS = args.N_BURSTS
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
||||||
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000
|
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400 # <- consider increasing if you get nread_exceptions > 0
|
# v-- consider increasing if you get nread_exceptions > 0
|
||||||
|
self.AUDIO_FRAMES_PER_BUFFER = 2400
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_TX = 48000
|
self.AUDIO_SAMPLE_RATE_TX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
self.transmit = True
|
self.transmit = True
|
||||||
|
|
||||||
self.resampler = codec2.resampler()
|
self.resampler = codec2.resampler()
|
||||||
|
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_OUTPUT_DEVICE != -1:
|
if self.AUDIO_OUTPUT_DEVICE != -1:
|
||||||
self.p = pyaudio.PyAudio()
|
self.p = pyaudio.PyAudio()
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_OUTPUT_DEVICE == -2:
|
if self.AUDIO_OUTPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = [
|
||||||
for dev in range(0,self.p.get_device_count()):
|
dev
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
for dev in range(self.p.get_device_count())
|
||||||
loopback_list.append(dev)
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]
|
||||||
|
]
|
||||||
|
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", file=sys.stderr)
|
f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} "
|
||||||
|
f"DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} "
|
||||||
|
f"AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_tx = self.p.open(format=pyaudio.paInt16,
|
self.stream_tx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_TX,
|
rate=self.AUDIO_SAMPLE_RATE_TX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=False,
|
input=False,
|
||||||
output=True,
|
output=True,
|
||||||
output_device_index=self.AUDIO_OUTPUT_DEVICE,
|
output_device_index=self.AUDIO_OUTPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
print("test_callback_multimode_tx: Not written for STDOUT usage.")
|
||||||
|
print("Exiting.")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.ftx = open("tx48_callback.raw", mode='wb')
|
self.ftx = open("tx48_callback.raw", mode="wb")
|
||||||
|
|
||||||
# data binary string
|
# data binary string
|
||||||
if args.TESTFRAMES:
|
if args.TESTFRAMES:
|
||||||
self.data_out = bytearray(14)
|
self.data_out = bytearray(14)
|
||||||
self.data_out[:1] = bytes([255])
|
self.data_out[:1] = bytes([255])
|
||||||
self.data_out[1:2] = bytes([1])
|
self.data_out[1:2] = bytes([1])
|
||||||
self.data_out[2:] = b'HELLO WORLD'
|
self.data_out[2:] = b"HELLO WORLD"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.data_out = b'HELLO WORLD!'
|
self.data_out = b"HELLO WORLD!"
|
||||||
|
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
|
|
||||||
|
@ -133,55 +157,76 @@ class Test():
|
||||||
|
|
||||||
def create_modulation(self):
|
def create_modulation(self):
|
||||||
|
|
||||||
modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3]
|
modes = [
|
||||||
|
codec2.api.FREEDV_MODE_DATAC0,
|
||||||
|
codec2.api.FREEDV_MODE_DATAC1,
|
||||||
|
codec2.api.FREEDV_MODE_DATAC3,
|
||||||
|
]
|
||||||
for m in modes:
|
for m in modes:
|
||||||
|
|
||||||
freedv = cast(codec2.api.freedv_open(m), c_void_p)
|
freedv = ctypes.cast(codec2.api.freedv_open(m), ctypes.c_void_p)
|
||||||
|
|
||||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||||
mod_out = create_string_buffer(2*n_tx_modem_samples)
|
mod_out = ctypes.create_string_buffer(2 * n_tx_modem_samples)
|
||||||
|
|
||||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
|
n_tx_preamble_modem_samples = (
|
||||||
mod_out_preamble = create_string_buffer(2*n_tx_preamble_modem_samples)
|
codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
|
||||||
|
)
|
||||||
|
mod_out_preamble = ctypes.create_string_buffer(
|
||||||
|
2 * n_tx_preamble_modem_samples
|
||||||
|
)
|
||||||
|
|
||||||
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
n_tx_postamble_modem_samples = (
|
||||||
mod_out_postamble = create_string_buffer(2*n_tx_postamble_modem_samples)
|
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||||
|
)
|
||||||
|
mod_out_postamble = ctypes.create_string_buffer(
|
||||||
|
2 * n_tx_postamble_modem_samples
|
||||||
|
)
|
||||||
|
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8
|
||||||
|
)
|
||||||
payload_per_frame = bytes_per_frame - 2
|
payload_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
buffer = bytearray(payload_per_frame)
|
buffer = bytearray(payload_per_frame)
|
||||||
# set buffersize to length of data which will be send
|
# set buffersize to length of data which will be send
|
||||||
buffer[:len(self.data_out)] = self.data_out
|
buffer[: len(self.data_out)] = self.data_out
|
||||||
|
|
||||||
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16
|
crc = ctypes.c_ushort(
|
||||||
|
codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)
|
||||||
|
) # generate CRC16
|
||||||
# convert crc to 2 byte hex string
|
# convert crc to 2 byte hex string
|
||||||
crc = crc.value.to_bytes(2, byteorder='big')
|
crc = crc.value.to_bytes(2, byteorder="big")
|
||||||
buffer += crc # append crc16 to buffer
|
buffer += crc # append crc16 to buffer
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
|
|
||||||
for i in range(1,self.N_BURSTS+1):
|
for i in range(1, self.N_BURSTS + 1):
|
||||||
|
|
||||||
# write preamble to txbuffer
|
# write preamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
txbuffer = bytes(mod_out_preamble)
|
txbuffer = bytes(mod_out_preamble)
|
||||||
|
|
||||||
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
||||||
for n in range(1,self.N_FRAMES_PER_BURST+1):
|
for n in range(1, self.N_FRAMES_PER_BURST + 1):
|
||||||
|
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer
|
codec2.api.freedv_rawdatatx(
|
||||||
|
freedv, mod_out, data
|
||||||
|
) # modulate DATA and save it into mod_out pointer
|
||||||
|
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
print(f"GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", file=sys.stderr)
|
print(
|
||||||
|
f"GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# append postamble to txbuffer
|
# append postamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||||
txbuffer += bytes(mod_out_postamble)
|
txbuffer += bytes(mod_out_postamble)
|
||||||
|
|
||||||
# append a delay between bursts as audio silence
|
# append a delay between bursts as audio silence
|
||||||
samples_delay = int(self.MODEM_SAMPLE_RATE*self.DELAY_BETWEEN_BURSTS)
|
samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS)
|
||||||
mod_out_silence = create_string_buffer(samples_delay*2)
|
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
|
||||||
txbuffer += bytes(mod_out_silence)
|
txbuffer += bytes(mod_out_silence)
|
||||||
|
|
||||||
# resample up to 48k (resampler works on np.int16)
|
# resample up to 48k (resampler works on np.int16)
|
||||||
|
@ -189,20 +234,23 @@ class Test():
|
||||||
txbuffer_48k = self.resampler.resample8_to_48(x)
|
txbuffer_48k = self.resampler.resample8_to_48(x)
|
||||||
|
|
||||||
# split modulated audio to chunks
|
# split modulated audio to chunks
|
||||||
#https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
|
# https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
|
||||||
txbuffer_48k = bytes(txbuffer_48k)
|
txbuffer_48k = bytes(txbuffer_48k)
|
||||||
chunk = [txbuffer_48k[i:i+self.AUDIO_FRAMES_PER_BUFFER*2] for i in range(0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER*2)]
|
chunk = [
|
||||||
|
txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2]
|
||||||
|
for i in range(
|
||||||
|
0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2
|
||||||
|
)
|
||||||
|
]
|
||||||
# add modulated chunks to fifo buffer
|
# add modulated chunks to fifo buffer
|
||||||
for c in chunk:
|
for c in chunk:
|
||||||
# if data is shorter than the expcected audio frames per buffer we need to append 0
|
# if data is shorter than the expcected audio frames per buffer we need to append 0
|
||||||
# to prevent the callback from stucking/crashing
|
# to prevent the callback from stucking/crashing
|
||||||
if len(c) < self.AUDIO_FRAMES_PER_BUFFER*2:
|
if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2:
|
||||||
c += bytes(self.AUDIO_FRAMES_PER_BUFFER*2 - len(c))
|
c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c))
|
||||||
self.dataqueue.put(c)
|
self.dataqueue.put(c)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test = Test()
|
test = Test()
|
||||||
test.create_modulation()
|
test.create_modulation()
|
||||||
test.run_audio()
|
test.run_audio()
|
||||||
|
|
|
@ -6,42 +6,56 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
parser.add_argument(
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int,
|
"--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
|
||||||
help="audio device number to use, use -2 to automatically select a loopback device")
|
)
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
"--audiodev",
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use, use -2 to automatically select a loopback device",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.N_BURSTS = args.N_BURSTS
|
self.N_BURSTS = args.N_BURSTS
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
@ -51,12 +65,16 @@ class Test():
|
||||||
self.TIMEOUT = args.TIMEOUT
|
self.TIMEOUT = args.TIMEOUT
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0
|
self.AUDIO_FRAMES_PER_BUFFER = (
|
||||||
|
2400 * 2
|
||||||
|
) # <- consider increasing if you get nread_exceptions > 0
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_RX = 48000
|
self.AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_INPUT_DEVICE != -1:
|
if self.AUDIO_INPUT_DEVICE != -1:
|
||||||
|
@ -64,37 +82,43 @@ class Test():
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_INPUT_DEVICE == -2:
|
if self.AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
for dev in range(0,self.p.get_device_count()):
|
for dev in range(self.p.get_device_count()):
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
|
||||||
loopback_list.append(dev)
|
loopback_list.append(dev)
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
|
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
||||||
|
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_rx = self.p.open(format=pyaudio.paInt16,
|
self.stream_rx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_RX,
|
rate=self.AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
output=False,
|
output=False,
|
||||||
input_device_index=self.AUDIO_INPUT_DEVICE,
|
input_device_index=self.AUDIO_INPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
|
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
# get number of bytes per frame for mode
|
||||||
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8)
|
self.bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
|
||||||
|
)
|
||||||
|
|
||||||
self.bytes_out = create_string_buffer(self.bytes_per_frame)
|
self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame)
|
||||||
|
|
||||||
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST)
|
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
|
||||||
|
|
||||||
self.total_n_bytes = 0
|
self.total_n_bytes = 0
|
||||||
self.rx_total_frames = 0
|
self.rx_total_frames = 0
|
||||||
|
@ -104,13 +128,13 @@ class Test():
|
||||||
self.nread_exceptions = 0
|
self.nread_exceptions = 0
|
||||||
self.timeout = time.time() + self.TIMEOUT
|
self.timeout = time.time() + self.TIMEOUT
|
||||||
self.receive = True
|
self.receive = True
|
||||||
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER*2)
|
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
self.resampler = codec2.resampler()
|
self.resampler = codec2.resampler()
|
||||||
|
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.frx = open("rx48_callback.raw", mode='wb')
|
self.frx = open("rx48_callback.raw", mode="wb")
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
|
|
||||||
|
@ -119,13 +143,14 @@ class Test():
|
||||||
x = self.resampler.resample48_to_8(x)
|
x = self.resampler.resample48_to_8(x)
|
||||||
self.audio_buffer.push(x)
|
self.audio_buffer.push(x)
|
||||||
|
|
||||||
|
|
||||||
# when we have enough samples call FreeDV Rx
|
# when we have enough samples call FreeDV Rx
|
||||||
nin = codec2.api.freedv_nin(self.freedv)
|
nin = codec2.api.freedv_nin(self.freedv)
|
||||||
while self.audio_buffer.nbuffer >= nin:
|
while self.audio_buffer.nbuffer >= nin:
|
||||||
|
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes
|
||||||
|
)
|
||||||
self.audio_buffer.pop(nin)
|
self.audio_buffer.pop(nin)
|
||||||
|
|
||||||
# call me on every loop!
|
# call me on every loop!
|
||||||
|
@ -136,8 +161,11 @@ class Test():
|
||||||
self.rx_errors = self.rx_errors + 1
|
self.rx_errors = self.rx_errors + 1
|
||||||
if self.DEBUGGING_MODE:
|
if self.DEBUGGING_MODE:
|
||||||
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
||||||
print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \
|
print(
|
||||||
(nin,rx_status,self.audio_buffer.nbuffer), file=sys.stderr)
|
"nin: %5d rx_status: %4s naudio_buffer: %4d"
|
||||||
|
% (nin, rx_status, self.audio_buffer.nbuffer),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
if nbytes:
|
if nbytes:
|
||||||
self.total_n_bytes = self.total_n_bytes + nbytes
|
self.total_n_bytes = self.total_n_bytes + nbytes
|
||||||
|
@ -162,7 +190,6 @@ class Test():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"pyAudio error: {e}", file=sys.stderr)
|
print(f"pyAudio error: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
while self.receive and time.time() < self.timeout:
|
while self.receive and time.time() < self.timeout:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
@ -170,9 +197,15 @@ class Test():
|
||||||
print("TIMEOUT REACHED")
|
print("TIMEOUT REACHED")
|
||||||
|
|
||||||
if self.nread_exceptions:
|
if self.nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
self.nread_exceptions, file=sys.stderr)
|
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
|
||||||
print(f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", file=sys.stderr)
|
% self.nread_exceptions,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
self.frx.close()
|
self.frx.close()
|
||||||
|
|
||||||
# cloese pyaudio instance
|
# cloese pyaudio instance
|
||||||
|
|
|
@ -6,42 +6,56 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
parser.add_argument(
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int,
|
"--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
|
||||||
help="audio device number to use, use -2 to automatically select a loopback device")
|
)
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
parser.add_argument(
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
"--audiodev",
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use, use -2 to automatically select a loopback device",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.N_BURSTS = args.N_BURSTS
|
self.N_BURSTS = args.N_BURSTS
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
@ -51,12 +65,16 @@ class Test():
|
||||||
self.TIMEOUT = args.TIMEOUT
|
self.TIMEOUT = args.TIMEOUT
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0
|
self.AUDIO_FRAMES_PER_BUFFER = (
|
||||||
|
2400 * 2
|
||||||
|
) # <- consider increasing if you get nread_exceptions > 0
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_RX = 48000
|
self.AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_INPUT_DEVICE != -1:
|
if self.AUDIO_INPUT_DEVICE != -1:
|
||||||
|
@ -64,37 +82,43 @@ class Test():
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_INPUT_DEVICE == -2:
|
if self.AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
for dev in range(0,self.p.get_device_count()):
|
for dev in range(self.p.get_device_count()):
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
|
||||||
loopback_list.append(dev)
|
loopback_list.append(dev)
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
|
f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \
|
||||||
|
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_rx = self.p.open(format=pyaudio.paInt16,
|
self.stream_rx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_RX,
|
rate=self.AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
output=False,
|
output=False,
|
||||||
input_device_index=self.AUDIO_INPUT_DEVICE,
|
input_device_index=self.AUDIO_INPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
|
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
# get number of bytes per frame for mode
|
||||||
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8)
|
self.bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
|
||||||
|
)
|
||||||
|
|
||||||
self.bytes_out = create_string_buffer(self.bytes_per_frame * 2)
|
self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame * 2)
|
||||||
|
|
||||||
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST)
|
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
|
||||||
|
|
||||||
self.total_n_bytes = 0
|
self.total_n_bytes = 0
|
||||||
self.rx_total_frames = 0
|
self.rx_total_frames = 0
|
||||||
|
@ -104,13 +128,13 @@ class Test():
|
||||||
self.nread_exceptions = 0
|
self.nread_exceptions = 0
|
||||||
self.timeout = time.time() + self.TIMEOUT
|
self.timeout = time.time() + self.TIMEOUT
|
||||||
self.receive = True
|
self.receive = True
|
||||||
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER*2)
|
self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
self.resampler = codec2.resampler()
|
self.resampler = codec2.resampler()
|
||||||
|
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.frx = open("rx48_callback.raw", mode='wb')
|
self.frx = open("rx48_callback.raw", mode="wb")
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
|
|
||||||
|
@ -128,15 +152,16 @@ class Test():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"pyAudio error: {e}", file=sys.stderr)
|
print(f"pyAudio error: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
while self.receive and time.time() < self.timeout:
|
while self.receive and time.time() < self.timeout:
|
||||||
#time.sleep(1)
|
# time.sleep(1)
|
||||||
# when we have enough samples call FreeDV Rx
|
# when we have enough samples call FreeDV Rx
|
||||||
nin = codec2.api.freedv_nin(self.freedv)
|
nin = codec2.api.freedv_nin(self.freedv)
|
||||||
while self.audio_buffer.nbuffer >= nin:
|
while self.audio_buffer.nbuffer >= nin:
|
||||||
|
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes
|
||||||
|
)
|
||||||
self.audio_buffer.pop(nin)
|
self.audio_buffer.pop(nin)
|
||||||
|
|
||||||
# call me on every loop!
|
# call me on every loop!
|
||||||
|
@ -147,8 +172,11 @@ class Test():
|
||||||
self.rx_errors = self.rx_errors + 1
|
self.rx_errors = self.rx_errors + 1
|
||||||
if self.DEBUGGING_MODE:
|
if self.DEBUGGING_MODE:
|
||||||
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
||||||
print("nin: %5d rx_status: %4s naudio_buffer: %4d" % \
|
print(
|
||||||
(nin,rx_status,self.audio_buffer.nbuffer), file=sys.stderr)
|
"nin: %5d rx_status: %4s naudio_buffer: %4d"
|
||||||
|
% (nin, rx_status, self.audio_buffer.nbuffer),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
if nbytes:
|
if nbytes:
|
||||||
self.total_n_bytes = self.total_n_bytes + nbytes
|
self.total_n_bytes = self.total_n_bytes + nbytes
|
||||||
|
@ -167,9 +195,15 @@ class Test():
|
||||||
print("TIMEOUT REACHED")
|
print("TIMEOUT REACHED")
|
||||||
|
|
||||||
if self.nread_exceptions:
|
if self.nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
self.nread_exceptions, file=sys.stderr)
|
"nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..."
|
||||||
print(f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", file=sys.stderr)
|
% self.nread_exceptions,
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
self.frx.close()
|
self.frx.close()
|
||||||
|
|
||||||
# cloese pyaudio instance
|
# cloese pyaudio instance
|
||||||
|
|
|
@ -6,43 +6,58 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
import queue
|
import queue
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import pyaudio
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA audio test')
|
parser = argparse.ArgumentParser(description="FreeDATA audio test")
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int)
|
||||||
parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
parser.add_argument(
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int,
|
"--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
|
||||||
help="audio device number to use, use -2 to automatically select a loopback device")
|
)
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
parser.add_argument(
|
||||||
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="generate testframes")
|
"--audiodev",
|
||||||
|
dest="AUDIO_OUTPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use, use -2 to automatically select a loopback device",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--testframes",
|
||||||
|
dest="TESTFRAMES",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="generate testframes",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
if args.LIST:
|
if args.LIST:
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
for dev in range(0,p.get_device_count()):
|
for dev in range(p.get_device_count()):
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
class Test():
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.dataqueue = queue.Queue()
|
self.dataqueue = queue.Queue()
|
||||||
|
@ -50,76 +65,81 @@ class Test():
|
||||||
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
||||||
self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
||||||
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000
|
self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
self.AUDIO_FRAMES_PER_BUFFER = 2400 # <- consider increasing if you get nread_exceptions > 0
|
# v-- consider increasing if you get nread_exceptions > 0
|
||||||
|
self.AUDIO_FRAMES_PER_BUFFER = 2400
|
||||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
self.AUDIO_SAMPLE_RATE_TX = 48000
|
self.AUDIO_SAMPLE_RATE_TX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (
|
||||||
|
self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE
|
||||||
|
) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
self.transmit = True
|
self.transmit = True
|
||||||
|
|
||||||
self.resampler = codec2.resampler()
|
self.resampler = codec2.resampler()
|
||||||
|
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if self.AUDIO_OUTPUT_DEVICE != -1:
|
if self.AUDIO_OUTPUT_DEVICE != -1:
|
||||||
self.p = pyaudio.PyAudio()
|
self.p = pyaudio.PyAudio()
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if self.AUDIO_OUTPUT_DEVICE == -2:
|
if self.AUDIO_OUTPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
for dev in range(0,self.p.get_device_count()):
|
for dev in range( self.p.get_device_count()):
|
||||||
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
|
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
|
||||||
loopback_list.append(dev)
|
loopback_list.append(dev)
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \
|
print(
|
||||||
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", file=sys.stderr)
|
f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \
|
||||||
|
AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
self.stream_tx = self.p.open(format=pyaudio.paInt16,
|
self.stream_tx = self.p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=self.AUDIO_SAMPLE_RATE_TX,
|
rate=self.AUDIO_SAMPLE_RATE_TX,
|
||||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=False,
|
input=False,
|
||||||
output=True,
|
output=True,
|
||||||
output_device_index=self.AUDIO_OUTPUT_DEVICE,
|
output_device_index=self.AUDIO_OUTPUT_DEVICE,
|
||||||
stream_callback=self.callback
|
stream_callback=self.callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
self.freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
|
self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
# get number of bytes per frame for mode
|
||||||
self.bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.freedv)/8)
|
self.bytes_per_frame = int(
|
||||||
|
codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8
|
||||||
|
)
|
||||||
|
|
||||||
self.bytes_out = create_string_buffer(self.bytes_per_frame)
|
self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame)
|
||||||
|
|
||||||
codec2.api.freedv_set_frames_per_burst(self.freedv,self.N_FRAMES_PER_BURST)
|
|
||||||
|
|
||||||
|
codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST)
|
||||||
|
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
# aplay -r 48000 -f S16_LE rx48_callback.raw
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
self.ftx = open("tx48_callback.raw", mode='wb')
|
self.ftx = open("tx48_callback.raw", mode="wb")
|
||||||
|
|
||||||
# data binary string
|
# data binary string
|
||||||
if args.TESTFRAMES:
|
if args.TESTFRAMES:
|
||||||
self.data_out = bytearray(14)
|
self.data_out = bytearray(14)
|
||||||
self.data_out[:1] = bytes([255])
|
self.data_out[:1] = bytes([255])
|
||||||
self.data_out[1:2] = bytes([1])
|
self.data_out[1:2] = bytes([1])
|
||||||
self.data_out[2:] = b'HELLO WORLD'
|
self.data_out[2:] = b"HELLO WORLD"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.data_out = b'HELLO WORLD!'
|
self.data_out = b"HELLO WORLD!"
|
||||||
|
|
||||||
|
|
||||||
def callback(self, data_in48k, frame_count, time_info, status):
|
def callback(self, data_in48k, frame_count, time_info, status):
|
||||||
|
|
||||||
|
@ -139,8 +159,6 @@ class Test():
|
||||||
sheeps = sheeps + 1
|
sheeps = sheeps + 1
|
||||||
print(f"counting sheeps...{sheeps}")
|
print(f"counting sheeps...{sheeps}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
self.ftx.close()
|
self.ftx.close()
|
||||||
|
|
||||||
# close pyaudio instance
|
# close pyaudio instance
|
||||||
|
@ -150,80 +168,103 @@ class Test():
|
||||||
def create_modulation(self):
|
def create_modulation(self):
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instance
|
||||||
freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
|
freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
# get number of bytes per frame for mode
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8)
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
payload_bytes_per_frame = bytes_per_frame -2
|
payload_bytes_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
# init buffer for data
|
# init buffer for data
|
||||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||||
mod_out = create_string_buffer(n_tx_modem_samples * 2)
|
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
||||||
|
|
||||||
# init buffer for preample
|
# init buffer for preample
|
||||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
|
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||||
mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2)
|
freedv
|
||||||
|
)
|
||||||
|
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
||||||
|
|
||||||
# init buffer for postamble
|
# init buffer for postamble
|
||||||
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
n_tx_postamble_modem_samples = (
|
||||||
mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2)
|
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||||
|
)
|
||||||
|
mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2)
|
||||||
|
|
||||||
# create buffer for data
|
# create buffer for data
|
||||||
buffer = bytearray(payload_bytes_per_frame) # use this if CRC16 checksum is required ( DATA1-3)
|
buffer = bytearray(
|
||||||
buffer[:len(self.data_out)] = self.data_out # set buffersize to length of data which will be send
|
payload_bytes_per_frame
|
||||||
|
) # use this if CRC16 checksum is required ( DATA1-3)
|
||||||
|
buffer[
|
||||||
|
: len(self.data_out)
|
||||||
|
] = self.data_out # set buffersize to length of data which will be send
|
||||||
|
|
||||||
# create crc for data frame - we are using the crc function shipped with codec2 to avoid
|
# create crc for data frame - we are using the crc function shipped with codec2 to avoid
|
||||||
# crc algorithm incompatibilities
|
# crc algorithm incompatibilities
|
||||||
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16
|
crc = ctypes.c_ushort(
|
||||||
crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string
|
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
|
||||||
|
) # generate CRC16
|
||||||
|
crc = crc.value.to_bytes(2, byteorder="big") # convert crc to 2 byte hex string
|
||||||
buffer += crc # append crc16 to buffer
|
buffer += crc # append crc16 to buffer
|
||||||
|
|
||||||
print(f"TOTAL BURSTS: {self.N_BURSTS} TOTAL FRAMES_PER_BURST: {self.N_FRAMES_PER_BURST}", file=sys.stderr)
|
print(
|
||||||
|
f"TOTAL BURSTS: {self.N_BURSTS} TOTAL FRAMES_PER_BURST: {self.N_FRAMES_PER_BURST}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
for i in range(1,self.N_BURSTS+1):
|
for i in range(1, self.N_BURSTS + 1):
|
||||||
|
|
||||||
# write preamble to txbuffer
|
# write preamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
txbuffer = bytes(mod_out_preamble)
|
txbuffer = bytes(mod_out_preamble)
|
||||||
|
|
||||||
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
||||||
for n in range(1,self.N_FRAMES_PER_BURST+1):
|
for n in range(1, self.N_FRAMES_PER_BURST + 1):
|
||||||
|
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer
|
codec2.api.freedv_rawdatatx(
|
||||||
|
freedv, mod_out, data
|
||||||
|
) # modulate DATA and save it into mod_out pointer
|
||||||
|
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
|
|
||||||
print(f" GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", file=sys.stderr)
|
print(
|
||||||
|
f" GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# append postamble to txbuffer
|
# append postamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||||
txbuffer += bytes(mod_out_postamble)
|
txbuffer += bytes(mod_out_postamble)
|
||||||
|
|
||||||
# append a delay between bursts as audio silence
|
# append a delay between bursts as audio silence
|
||||||
samples_delay = int(self.MODEM_SAMPLE_RATE*self.DELAY_BETWEEN_BURSTS)
|
samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS)
|
||||||
mod_out_silence = create_string_buffer(samples_delay*2)
|
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
|
||||||
txbuffer += bytes(mod_out_silence)
|
txbuffer += bytes(mod_out_silence)
|
||||||
print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {self.DELAY_BETWEEN_BURSTS}", file=sys.stderr)
|
print(
|
||||||
|
f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {self.DELAY_BETWEEN_BURSTS}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# resample up to 48k (resampler works on np.int16)
|
# resample up to 48k (resampler works on np.int16)
|
||||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
x = np.frombuffer(txbuffer, dtype=np.int16)
|
||||||
txbuffer_48k = self.resampler.resample8_to_48(x)
|
txbuffer_48k = self.resampler.resample8_to_48(x)
|
||||||
|
|
||||||
# split modualted audio to chunks
|
# split modualted audio to chunks
|
||||||
#https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
|
# https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
|
||||||
txbuffer_48k = bytes(txbuffer_48k)
|
txbuffer_48k = bytes(txbuffer_48k)
|
||||||
chunk = [txbuffer_48k[i:i+self.AUDIO_FRAMES_PER_BUFFER*2] for i in range(0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER*2)]
|
chunk = [
|
||||||
|
txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2]
|
||||||
|
for i in range( len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
|
]
|
||||||
# add modulated chunks to fifo buffer
|
# add modulated chunks to fifo buffer
|
||||||
for c in chunk:
|
for c in chunk:
|
||||||
# if data is shorter than the expcected audio frames per buffer we need to append 0
|
# if data is shorter than the expcected audio frames per buffer we need to append 0
|
||||||
# to prevent the callback from stucking/crashing
|
# to prevent the callback from stucking/crashing
|
||||||
if len(c) < self.AUDIO_FRAMES_PER_BUFFER*2:
|
if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2:
|
||||||
c += bytes(self.AUDIO_FRAMES_PER_BUFFER*2 - len(c))
|
c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c))
|
||||||
self.dataqueue.put(c)
|
self.dataqueue.put(c)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test = Test()
|
test = Test()
|
||||||
test.create_modulation()
|
test.create_modulation()
|
||||||
test.run_audio()
|
test.run_audio()
|
||||||
|
|
90
test/test_helpers.py
Normal file
90
test/test_helpers.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"""
|
||||||
|
Tests for the FreeDATA TNC state machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import helpers
|
||||||
|
import pytest
|
||||||
|
import static
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("callsign", ["AA1AA", "DE2DE", "E4AWQ-4"])
|
||||||
|
def test_check_callsign(callsign: str):
|
||||||
|
"""
|
||||||
|
Execute test to demonstrate how to create and verify callsign checksums.
|
||||||
|
"""
|
||||||
|
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||||
|
|
||||||
|
t_callsign_bytes = helpers.callsign_to_bytes(callsign)
|
||||||
|
t_callsign = helpers.bytes_to_callsign(t_callsign_bytes)
|
||||||
|
t_callsign_crc = helpers.get_crc_24(t_callsign)
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes("ZZ9ZZA-0")
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
|
assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True
|
||||||
|
assert helpers.check_callsign(t_callsign, dxcallsign_crc)[0] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
|
||||||
|
def test_callsign_to_bytes(callsign: str):
|
||||||
|
"""
|
||||||
|
Execute test to demonsrate symmetry when converting callsigns to and from byte arrays.
|
||||||
|
"""
|
||||||
|
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||||
|
|
||||||
|
t_callsign_crc = helpers.get_crc_24(bytes(callsign, "UTF-8"))
|
||||||
|
t_callsign_bytes = helpers.callsign_to_bytes(callsign)
|
||||||
|
t_callsign = helpers.bytes_to_callsign(t_callsign_bytes)
|
||||||
|
|
||||||
|
assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True
|
||||||
|
assert helpers.check_callsign(t_callsign, t_callsign_crc)[1] == bytes(
|
||||||
|
callsign, "UTF-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "e4awq-4"])
|
||||||
|
def test_encode_callsign(callsign: str):
|
||||||
|
"""
|
||||||
|
Execute test to demonsrate symmetry when encoding and decoding callsigns.
|
||||||
|
"""
|
||||||
|
callenc = helpers.encode_call(callsign)
|
||||||
|
calldec = helpers.decode_call(callenc)
|
||||||
|
|
||||||
|
assert callsign.upper() != calldec
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("gridsq", ["EM98dc", "DE01GG", "EF42sW"])
|
||||||
|
def test_encode_grid(gridsq: str):
|
||||||
|
"""
|
||||||
|
Execute test to demonsrate symmetry when encoding and decoding grid squares.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gridenc = helpers.encode_grid(gridsq)
|
||||||
|
griddec = helpers.decode_grid(gridenc)
|
||||||
|
|
||||||
|
assert gridsq.upper() == griddec
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("gridsq", ["SM98dc", "DE01GZ", "EV42sY"])
|
||||||
|
@pytest.mark.xfail(reason="Invalid gridsquare provided")
|
||||||
|
def test_invalid_grid(gridsq: str):
|
||||||
|
"""
|
||||||
|
Execute test to demonsrate symmetry when encoding and decoding grid squares.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gridenc = helpers.encode_grid(gridsq)
|
||||||
|
griddec = helpers.decode_grid(gridenc)
|
||||||
|
|
||||||
|
assert gridsq.upper() != griddec
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
123
test/test_highsnr_stdio_C_P_datacx.py
Normal file
123
test/test_highsnr_stdio_C_P_datacx.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
Tests a high signal-to-noise ratio path with codec2 data formats using codec2 to transmit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name, unused-import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
BURSTS = int(os.environ["BURSTS"])
|
||||||
|
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
|
||||||
|
TESTFRAMES = int(os.environ["TESTFRAMES"])
|
||||||
|
except KeyError:
|
||||||
|
BURSTS = 1
|
||||||
|
FRAMESPERBURST = 1
|
||||||
|
TESTFRAMES = 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
|
||||||
|
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
|
||||||
|
@pytest.mark.parametrize("testframes", [TESTFRAMES, 2, 1])
|
||||||
|
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
|
||||||
|
def test_HighSNR_P_P_DATACx(
|
||||||
|
bursts: int, frames_per_burst: int, testframes: int, mode: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test a high signal-to-noise ratio path with DATAC0.
|
||||||
|
|
||||||
|
:param bursts: Number of bursts
|
||||||
|
:type bursts: str
|
||||||
|
:param frames_per_burst: Number of frames transmitted per burst
|
||||||
|
:type frames_per_burst: str
|
||||||
|
:param testframes: Number of test frames to transmit
|
||||||
|
:type testframes: str
|
||||||
|
"""
|
||||||
|
tx_side = "freedv_data_raw_tx"
|
||||||
|
|
||||||
|
# Facilitate running from main directory as well as inside test/
|
||||||
|
rx_side = "test_rx.py"
|
||||||
|
if os.path.exists("test") and os.path.exists(os.path.join("test", rx_side)):
|
||||||
|
rx_side = os.path.join("test", rx_side)
|
||||||
|
os.environ["PYTHONPATH"] += ":."
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
tx_side,
|
||||||
|
mode,
|
||||||
|
"--testframes",
|
||||||
|
f"{testframes}",
|
||||||
|
"--bursts",
|
||||||
|
f"{bursts}",
|
||||||
|
"--framesperburst",
|
||||||
|
f"{frames_per_burst}",
|
||||||
|
"/dev/zero",
|
||||||
|
"-",
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
) as transmit:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"sox",
|
||||||
|
"-t",
|
||||||
|
".s16",
|
||||||
|
"-r",
|
||||||
|
"8000",
|
||||||
|
"-c",
|
||||||
|
"1",
|
||||||
|
"-",
|
||||||
|
"-t",
|
||||||
|
".s16",
|
||||||
|
"-r",
|
||||||
|
"48000",
|
||||||
|
"-c",
|
||||||
|
"1",
|
||||||
|
"-",
|
||||||
|
],
|
||||||
|
stdin=transmit.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as sox_filter:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
rx_side,
|
||||||
|
"--mode",
|
||||||
|
mode,
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
"--bursts",
|
||||||
|
str(bursts),
|
||||||
|
],
|
||||||
|
stdin=sox_filter.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as receive:
|
||||||
|
assert receive.stdout
|
||||||
|
lastline = "".join(
|
||||||
|
[
|
||||||
|
str(line, "UTF-8")
|
||||||
|
for line in receive.stdout.readlines()
|
||||||
|
if "RECEIVED " in str(line, "UTF-8")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert f"RECEIVED BURSTS: {bursts}" in lastline
|
||||||
|
assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline
|
||||||
|
assert "RX_ERRORS: 0" in lastline
|
||||||
|
print(lastline)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", "-s", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
126
test/test_highsnr_stdio_P_C_datacx.py
Normal file
126
test/test_highsnr_stdio_P_C_datacx.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
"""
|
||||||
|
Tests a high signal-to-noise ratio path with codec2 data formats using codec2 to receive.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name, unused-import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
BURSTS = int(os.environ["BURSTS"])
|
||||||
|
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
|
||||||
|
TESTFRAMES = int(os.environ["TESTFRAMES"])
|
||||||
|
except KeyError:
|
||||||
|
BURSTS = 1
|
||||||
|
FRAMESPERBURST = 1
|
||||||
|
TESTFRAMES = 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
|
||||||
|
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
|
||||||
|
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
|
||||||
|
def test_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str):
|
||||||
|
"""
|
||||||
|
Test a high signal-to-noise ratio path with DATAC0.
|
||||||
|
|
||||||
|
:param bursts: Number of bursts
|
||||||
|
:type bursts: str
|
||||||
|
:param frames_per_burst: Number of frames transmitted per burst
|
||||||
|
:type frames_per_burst: str
|
||||||
|
:param testframes: Number of test frames to transmit
|
||||||
|
:type testframes: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Facilitate running from main directory as well as inside test/
|
||||||
|
tx_side = "test_tx.py"
|
||||||
|
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
|
||||||
|
tx_side = os.path.join("test", tx_side)
|
||||||
|
os.environ["PYTHONPATH"] += ":."
|
||||||
|
rx_side = "freedv_data_raw_rx"
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
tx_side,
|
||||||
|
"--mode",
|
||||||
|
mode,
|
||||||
|
"--delay",
|
||||||
|
"500",
|
||||||
|
"--framesperburst",
|
||||||
|
f"{frames_per_burst}",
|
||||||
|
"--bursts",
|
||||||
|
f"{bursts}",
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
) as transmit:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"sox",
|
||||||
|
"-t",
|
||||||
|
".s16",
|
||||||
|
"-r",
|
||||||
|
"48000",
|
||||||
|
"-c",
|
||||||
|
"1",
|
||||||
|
"-",
|
||||||
|
"-t",
|
||||||
|
".s16",
|
||||||
|
"-r",
|
||||||
|
"8000",
|
||||||
|
"-c",
|
||||||
|
"1",
|
||||||
|
"-",
|
||||||
|
],
|
||||||
|
stdin=transmit.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as sox_filter:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
rx_side,
|
||||||
|
mode,
|
||||||
|
"-",
|
||||||
|
"-",
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
],
|
||||||
|
stdin=sox_filter.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as receive:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"hexdump",
|
||||||
|
"-C",
|
||||||
|
],
|
||||||
|
stdin=receive.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as hexdump:
|
||||||
|
assert hexdump.stdout
|
||||||
|
lastline = "".join(
|
||||||
|
[
|
||||||
|
str(line, "UTF-8")
|
||||||
|
for line in hexdump.stdout.readlines()
|
||||||
|
if "HELLO" in str(line, "UTF-8")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert "HELLO WORLD!" in lastline
|
||||||
|
print(lastline)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", "-s", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
97
test/test_highsnr_stdio_P_P_datacx.py
Normal file
97
test/test_highsnr_stdio_P_P_datacx.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
"""
|
||||||
|
Tests a high signal-to-noise ratio path with codec2 data formats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name, unused-import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
BURSTS = int(os.environ["BURSTS"])
|
||||||
|
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
|
||||||
|
TESTFRAMES = int(os.environ["TESTFRAMES"])
|
||||||
|
except KeyError:
|
||||||
|
BURSTS = 1
|
||||||
|
FRAMESPERBURST = 1
|
||||||
|
TESTFRAMES = 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
|
||||||
|
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
|
||||||
|
@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"])
|
||||||
|
def test_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str):
|
||||||
|
"""
|
||||||
|
Test a high signal-to-noise ratio path with Codec2 modes.
|
||||||
|
|
||||||
|
:param bursts: Number of bursts
|
||||||
|
:type bursts: str
|
||||||
|
:param frames_per_burst: Number of frames transmitted per burst
|
||||||
|
:type frames_per_burst: str
|
||||||
|
"""
|
||||||
|
# Facilitate running from main directory as well as inside test/
|
||||||
|
tx_side = "test_tx.py"
|
||||||
|
rx_side = "test_rx.py"
|
||||||
|
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
|
||||||
|
tx_side = os.path.join("test", tx_side)
|
||||||
|
rx_side = os.path.join("test", rx_side)
|
||||||
|
os.environ["PYTHONPATH"] += ":."
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
tx_side,
|
||||||
|
"--mode",
|
||||||
|
mode,
|
||||||
|
"--delay",
|
||||||
|
"500",
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
"--bursts",
|
||||||
|
str(bursts),
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
) as transmit:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
rx_side,
|
||||||
|
"--mode",
|
||||||
|
mode,
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
"--bursts",
|
||||||
|
str(bursts),
|
||||||
|
"--timeout",
|
||||||
|
"20",
|
||||||
|
],
|
||||||
|
stdin=transmit.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as receive:
|
||||||
|
assert receive.stdout
|
||||||
|
lastline = "".join(
|
||||||
|
[
|
||||||
|
str(line, "UTF-8")
|
||||||
|
for line in receive.stdout.readlines()
|
||||||
|
if "RECEIVED " in str(line, "UTF-8")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert f"RECEIVED BURSTS: {bursts}" in lastline
|
||||||
|
assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline
|
||||||
|
assert "RX_ERRORS: 0" in lastline
|
||||||
|
print(lastline)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", "-s", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
92
test/test_highsnr_stdio_P_P_multi.py
Normal file
92
test/test_highsnr_stdio_P_P_multi.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
"""
|
||||||
|
Tests a high signal-to-noise ratio path with multiple codec2 data formats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name, unused-import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
BURSTS = int(os.environ["BURSTS"])
|
||||||
|
FRAMESPERBURST = int(os.environ["FRAMESPERBURST"])
|
||||||
|
TESTFRAMES = int(os.environ["TESTFRAMES"])
|
||||||
|
except KeyError:
|
||||||
|
BURSTS = 1
|
||||||
|
FRAMESPERBURST = 1
|
||||||
|
TESTFRAMES = 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bursts", [BURSTS, 2, 3])
|
||||||
|
@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3])
|
||||||
|
def test_HighSNR_P_P_Multi(bursts: int, frames_per_burst: int):
|
||||||
|
"""
|
||||||
|
Test a high signal-to-noise ratio path with DATAC0, DATAC1 and DATAC3.
|
||||||
|
|
||||||
|
:param bursts: Number of bursts
|
||||||
|
:type bursts: int
|
||||||
|
:param frames_per_burst: Number of frames transmitted per burst
|
||||||
|
:type frames_per_burst: int
|
||||||
|
"""
|
||||||
|
# Facilitate running from main directory as well as inside test/
|
||||||
|
tx_side = "test_multimode_tx.py"
|
||||||
|
rx_side = "test_multimode_rx.py"
|
||||||
|
if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)):
|
||||||
|
tx_side = os.path.join("test", tx_side)
|
||||||
|
rx_side = os.path.join("test", rx_side)
|
||||||
|
os.environ["PYTHONPATH"] += ":."
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
tx_side,
|
||||||
|
"--delay",
|
||||||
|
"500",
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
"--bursts",
|
||||||
|
str(bursts),
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
) as transmit:
|
||||||
|
|
||||||
|
with subprocess.Popen(
|
||||||
|
args=[
|
||||||
|
"python3",
|
||||||
|
rx_side,
|
||||||
|
"--framesperburst",
|
||||||
|
str(frames_per_burst),
|
||||||
|
"--bursts",
|
||||||
|
str(bursts),
|
||||||
|
"--timeout",
|
||||||
|
"20",
|
||||||
|
],
|
||||||
|
stdin=transmit.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
) as receive:
|
||||||
|
assert receive.stdout
|
||||||
|
lastline = "".join(
|
||||||
|
[
|
||||||
|
str(line, "UTF-8")
|
||||||
|
for line in receive.stdout.readlines()
|
||||||
|
if "DATAC" in str(line, "UTF-8")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert f"DATAC0: {bursts}/{frames_per_burst * bursts}" in lastline
|
||||||
|
assert f"DATAC1: {bursts}/{frames_per_burst * bursts}" in lastline
|
||||||
|
assert f"DATAC3: {bursts}/{frames_per_burst * bursts}" in lastline
|
||||||
|
print(lastline)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", "-s", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
160
test/test_modem.py
Normal file
160
test/test_modem.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
"""
|
||||||
|
Tests for the FreeDATA modem.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
|
sys.path.insert(0, "../tnc")
|
||||||
|
import helpers
|
||||||
|
import modem
|
||||||
|
import static
|
||||||
|
|
||||||
|
|
||||||
|
def print_frame(data: bytearray):
|
||||||
|
"""
|
||||||
|
Pretty-print the provided frame.
|
||||||
|
|
||||||
|
:param data: Frame to be output
|
||||||
|
:type data: bytearray
|
||||||
|
"""
|
||||||
|
print(f"Type : {int(data[0])}")
|
||||||
|
print(f"DXCRC : {bytes(data[1:4])}")
|
||||||
|
print(f"CallCRC: {bytes(data[4:7])}")
|
||||||
|
print(f"Call : {helpers.bytes_to_callsign(data[7:13])}")
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the requested frame.
|
||||||
|
|
||||||
|
:param frame_type: The numerical type of the desired frame.
|
||||||
|
:type frame_type: int
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
||||||
|
mycallsign_crc = helpers.get_crc_24(mycallsign)
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
|
frame = bytearray(14)
|
||||||
|
frame[:1] = bytes([frame_type])
|
||||||
|
frame[1:4] = dxcallsign_crc
|
||||||
|
frame[4:7] = mycallsign_crc
|
||||||
|
frame[7:13] = mycallsign_bytes
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the session_close frame.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
return t_create_frame(223, mycall, dxcall)
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the create_session frame.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
return t_create_frame(221, mycall, dxcall)
|
||||||
|
|
||||||
|
|
||||||
|
def t_tsh_dummy():
|
||||||
|
"""Replacement function for transmit"""
|
||||||
|
print("In t_tsh_dummy")
|
||||||
|
|
||||||
|
|
||||||
|
def t_modem():
|
||||||
|
"""
|
||||||
|
Execute test to validate that receiving a session open frame sets the correct machine
|
||||||
|
state.
|
||||||
|
"""
|
||||||
|
t_mode = t_repeats = t_repeat_delay = 0
|
||||||
|
t_frames = []
|
||||||
|
|
||||||
|
# enable testmode
|
||||||
|
modem.TESTMODE = True
|
||||||
|
modem.RXCHANNEL = "/tmp/hfchannel1"
|
||||||
|
modem.TXCHANNEL = "/tmp/hfchannel2"
|
||||||
|
static.HAMLIB_RADIOCONTROL = "disabled"
|
||||||
|
|
||||||
|
def t_tx_dummy(mode, repeats, repeat_delay, frames):
|
||||||
|
"""Replacement function for transmit"""
|
||||||
|
print(f"t_tx_dummy: In transmit({mode}, {repeats}, {repeat_delay}, {frames})")
|
||||||
|
nonlocal t_mode, t_repeats, t_repeat_delay, t_frames
|
||||||
|
t_mode = mode
|
||||||
|
t_repeats = repeats
|
||||||
|
t_repeat_delay = repeat_delay
|
||||||
|
t_frames = frames[:]
|
||||||
|
static.TRANSMITTING = False
|
||||||
|
|
||||||
|
# Create the modem
|
||||||
|
local_modem = modem.RF()
|
||||||
|
|
||||||
|
# Replace transmit routine with our own, an effective No-Op.
|
||||||
|
local_modem.transmit = t_tx_dummy
|
||||||
|
|
||||||
|
txbuffer = t_create_start_session("AA9AA", "DC2EJ")
|
||||||
|
|
||||||
|
# Start the transmission
|
||||||
|
static.TRANSMITTING = True
|
||||||
|
modem.MODEM_TRANSMIT_QUEUE.put([14, 5, 250, txbuffer])
|
||||||
|
while static.TRANSMITTING:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Check that the contents were transferred correctly.
|
||||||
|
assert t_mode == 14
|
||||||
|
assert t_repeats == 5
|
||||||
|
assert t_repeat_delay == 250
|
||||||
|
assert t_frames == txbuffer
|
||||||
|
|
||||||
|
|
||||||
|
def test_modem_queue():
|
||||||
|
proc = multiprocessing.Process(target=t_modem, args=())
|
||||||
|
# print("Starting threads.")
|
||||||
|
proc.start()
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# print("Terminating threads.")
|
||||||
|
proc.terminate()
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
# print(f"\n{proc.exitcode=}")
|
||||||
|
assert proc.exitcode == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", "-s", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
|
@ -1,236 +1,242 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import pyaudio
|
|
||||||
import audioop
|
|
||||||
import time
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes import *
|
import sys
|
||||||
import pathlib
|
import time
|
||||||
sys.path.insert(0,'..')
|
from typing import List
|
||||||
from tnc import codec2
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pyaudio
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
sys.path.insert(0, "..")
|
||||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
from tnc import codec2
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int, help="audio device number to use")
|
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.LIST:
|
def test_mm_rx():
|
||||||
p = pyaudio.PyAudio()
|
# AUDIO PARAMETERS
|
||||||
for dev in range(0,p.get_device_count()):
|
AUDIO_FRAMES_PER_BUFFER = 2400 * 2
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
quit()
|
AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
# make sure our resampler will work
|
||||||
|
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
# SET COUNTERS
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
rx_bursts_datac = [0, 0, 0]
|
||||||
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
|
rx_frames_datac = [0, 0, 0]
|
||||||
DEBUGGING_MODE = args.DEBUGGING_MODE
|
rx_total_frames_datac = [0, 0, 0]
|
||||||
TIMEOUT = args.TIMEOUT
|
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# time meassurement
|
||||||
AUDIO_FRAMES_PER_BUFFER = 2400*2
|
time_end_datac = [0.0, 0.0, 0.0]
|
||||||
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
time_needed_datac = [0.0, 0.0, 0.0]
|
||||||
AUDIO_SAMPLE_RATE_RX = 48000
|
time_start_datac = [0.0, 0.0, 0.0]
|
||||||
# make sure our resampler will work
|
|
||||||
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
|
||||||
|
|
||||||
# SET COUNTERS
|
datac_buffer: List[codec2.audio_buffer] = []
|
||||||
rx_total_frames_datac0 = 0
|
datac_bytes_out: List[ctypes.Array] = []
|
||||||
rx_frames_datac0 = 0
|
datac_bytes_per_frame = []
|
||||||
rx_bursts_datac0 = 0
|
datac_freedv: List[ctypes.c_void_p] = []
|
||||||
|
|
||||||
rx_total_frames_datac1 = 0
|
args = parse_arguments()
|
||||||
rx_frames_datac1 = 0
|
|
||||||
rx_bursts_datac1 = 0
|
|
||||||
|
|
||||||
rx_total_frames_datac3 = 0
|
if args.LIST:
|
||||||
rx_frames_datac3 = 0
|
p_audio = pyaudio.PyAudio()
|
||||||
rx_bursts_datac3 = 0
|
for dev in range(p_audio.get_device_count()):
|
||||||
|
print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"])
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# time meassurement
|
N_BURSTS = args.N_BURSTS
|
||||||
time_start_datac0 = 0
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
time_end_datac0 = 0
|
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
|
||||||
time_start_datac1 = 0
|
DEBUGGING_MODE = args.DEBUGGING_MODE
|
||||||
time_end_datac1 = 0
|
MAX_TIME = args.TIMEOUT
|
||||||
time_start_datac3 = 0
|
|
||||||
time_end_datac3 = 0
|
|
||||||
time_needed_datac0 = 0
|
|
||||||
time_needed_datac1 = 0
|
|
||||||
time_needed_datac3 = 0
|
|
||||||
|
|
||||||
# open codec2 instance
|
# open codec2 instances
|
||||||
datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p)
|
for idx in range(3):
|
||||||
datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac0_freedv)/8)
|
datac_freedv.append(
|
||||||
datac0_bytes_out = create_string_buffer(datac0_bytes_per_frame)
|
ctypes.cast(
|
||||||
codec2.api.freedv_set_frames_per_burst(datac0_freedv,N_FRAMES_PER_BURST)
|
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
|
||||||
datac0_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER)
|
)
|
||||||
|
)
|
||||||
|
datac_bytes_per_frame.append(
|
||||||
|
int(codec2.api.freedv_get_bits_per_modem_frame(datac_freedv[idx]) / 8)
|
||||||
|
)
|
||||||
|
datac_bytes_out.append(ctypes.create_string_buffer(datac_bytes_per_frame[idx]))
|
||||||
|
codec2.api.freedv_set_frames_per_burst(datac_freedv[idx], N_FRAMES_PER_BURST)
|
||||||
|
datac_buffer.append(codec2.audio_buffer(2 * AUDIO_FRAMES_PER_BUFFER))
|
||||||
|
|
||||||
datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p)
|
resampler = codec2.resampler()
|
||||||
datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac1_freedv)/8)
|
|
||||||
datac1_bytes_out = create_string_buffer(datac1_bytes_per_frame)
|
|
||||||
codec2.api.freedv_set_frames_per_burst(datac1_freedv,N_FRAMES_PER_BURST)
|
|
||||||
datac1_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER)
|
|
||||||
|
|
||||||
datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(datac3_freedv)/8)
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
datac3_bytes_out = create_string_buffer(datac3_bytes_per_frame)
|
p_audio = pyaudio.PyAudio()
|
||||||
codec2.api.freedv_set_frames_per_burst(datac3_freedv,N_FRAMES_PER_BURST)
|
|
||||||
datac3_buffer = codec2.audio_buffer(2*AUDIO_FRAMES_PER_BUFFER)
|
|
||||||
|
|
||||||
resampler = codec2.resampler()
|
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if AUDIO_INPUT_DEVICE == -2:
|
if AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = [
|
||||||
for dev in range(0,p.get_device_count()):
|
dev
|
||||||
if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]:
|
for dev in range(p_audio.get_device_count())
|
||||||
loopback_list.append(dev)
|
if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"]
|
||||||
|
]
|
||||||
|
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} DEVICE: {p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}", file=sys.stderr)
|
print(
|
||||||
stream_rx = p.open(format=pyaudio.paInt16,
|
f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} "
|
||||||
|
f"DEVICE: {p_audio.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} "
|
||||||
|
f"AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
stream_rx = p_audio.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=AUDIO_SAMPLE_RATE_RX,
|
rate=AUDIO_SAMPLE_RATE_RX,
|
||||||
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
|
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER,
|
||||||
input=True,
|
input=True,
|
||||||
input_device_index=AUDIO_INPUT_DEVICE
|
input_device_index=AUDIO_INPUT_DEVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
timeout = time.time() + MAX_TIME
|
||||||
|
print(time.time(), MAX_TIME, timeout)
|
||||||
|
receive = True
|
||||||
|
nread_exceptions = 0
|
||||||
|
|
||||||
timeout = time.time() + TIMEOUT
|
# initial nin values
|
||||||
print(time.time(),TIMEOUT, timeout)
|
datac_nin = [0, 0, 0]
|
||||||
receive = True
|
for idx in range(3):
|
||||||
nread_exceptions = 0
|
datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx])
|
||||||
|
|
||||||
# initial nin values
|
def print_stats(time_datac0, time_datac1, time_datac3):
|
||||||
datac0_nin = codec2.api.freedv_nin(datac0_freedv)
|
if not DEBUGGING_MODE:
|
||||||
datac1_nin = codec2.api.freedv_nin(datac1_freedv)
|
return
|
||||||
datac3_nin = codec2.api.freedv_nin(datac3_freedv)
|
|
||||||
|
|
||||||
def print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3):
|
time_datac = [time_datac0, time_datac1, time_datac3]
|
||||||
if DEBUGGING_MODE:
|
datac_rxstatus = ["", "", ""]
|
||||||
datac0_rxstatus = codec2.api.freedv_get_rx_status(datac0_freedv)
|
for idx in range(3):
|
||||||
datac0_rxstatus = codec2.api.rx_sync_flags_to_text[datac0_rxstatus]
|
datac_rxstatus[idx] = codec2.api.rx_sync_flags_to_text[
|
||||||
|
codec2.api.freedv_get_rx_status(datac_freedv[idx])
|
||||||
|
]
|
||||||
|
|
||||||
datac1_rxstatus = codec2.api.freedv_get_rx_status(datac1_freedv)
|
text_out = ""
|
||||||
datac1_rxstatus = codec2.api.rx_sync_flags_to_text[datac1_rxstatus]
|
for idx in range(3):
|
||||||
|
text_out += f"NIN{idx}: {datac_nin[idx]:5d} "
|
||||||
|
text_out += f"RX_STATUS{idx}: {datac_rxstatus[idx]:4s} "
|
||||||
|
text_out += f"TIME: {round(time_datac[idx], 4):.4f} | "
|
||||||
|
text_out = text_out.rstrip(" ").rstrip("|").rstrip(" ")
|
||||||
|
print(text_out, file=sys.stderr)
|
||||||
|
|
||||||
datac3_rxstatus = codec2.api.freedv_get_rx_status(datac3_freedv)
|
while receive and time.time() < timeout:
|
||||||
datac3_rxstatus = codec2.api.rx_sync_flags_to_text[datac3_rxstatus]
|
|
||||||
|
|
||||||
print("NIN0: %5d RX_STATUS0: %4s TIME: %4s | NIN1: %5d RX_STATUS1: %4s TIME: %4s | NIN3: %5d RX_STATUS3: %4s TIME: %4s" % \
|
|
||||||
(datac0_nin, datac0_rxstatus, round(time_needed_datac0, 4), datac1_nin, datac1_rxstatus, round(time_needed_datac1, 4) ,datac3_nin, datac3_rxstatus, round(time_needed_datac3, 4)),
|
|
||||||
file=sys.stderr)
|
|
||||||
|
|
||||||
while receive and time.time() < timeout:
|
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
try:
|
try:
|
||||||
data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
|
data_in48k = stream_rx.read(
|
||||||
|
AUDIO_FRAMES_PER_BUFFER, exception_on_overflow=True
|
||||||
|
)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
print(err, file=sys.stderr)
|
print(err, file=sys.stderr)
|
||||||
if str(err).find("Input overflowed") != -1:
|
if "Input overflowed" in str(err):
|
||||||
nread_exceptions += 1
|
nread_exceptions += 1
|
||||||
if str(err).find("Stream closed") != -1:
|
if "Stream closed" in str(err):
|
||||||
print("Ending....")
|
print("Ending....")
|
||||||
receive = False
|
receive = False
|
||||||
else:
|
else:
|
||||||
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2)
|
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
|
|
||||||
# insert samples in buffer
|
# insert samples in buffer
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
audio_buffer = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
if len(x) != AUDIO_FRAMES_PER_BUFFER:
|
if len(audio_buffer) != AUDIO_FRAMES_PER_BUFFER:
|
||||||
print("len(x)",len(x))
|
print("len(x)", len(audio_buffer))
|
||||||
receive = False
|
receive = False
|
||||||
x = resampler.resample48_to_8(x)
|
audio_buffer = resampler.resample48_to_8(audio_buffer)
|
||||||
|
|
||||||
datac0_buffer.push(x)
|
for idx in range(3):
|
||||||
datac1_buffer.push(x)
|
datac_buffer[idx].push(audio_buffer)
|
||||||
datac3_buffer.push(x)
|
while datac_buffer[idx].nbuffer >= datac_nin[idx]:
|
||||||
print_something = False
|
|
||||||
|
|
||||||
while datac0_buffer.nbuffer >= datac0_nin:
|
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
time_start_datac0 = time.time()
|
time_start_datac[idx] = time.time()
|
||||||
nbytes = codec2.api.freedv_rawdatarx(datac0_freedv, datac0_bytes_out, datac0_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
time_end_datac0 = time.time()
|
datac_freedv[idx],
|
||||||
datac0_buffer.pop(datac0_nin)
|
datac_bytes_out[idx],
|
||||||
datac0_nin = codec2.api.freedv_nin(datac0_freedv)
|
datac_buffer[idx].buffer.ctypes,
|
||||||
if nbytes == datac0_bytes_per_frame:
|
)
|
||||||
rx_total_frames_datac0 = rx_total_frames_datac0 + 1
|
time_end_datac[idx] = time.time()
|
||||||
rx_frames_datac0 = rx_frames_datac0 + 1
|
datac_buffer[idx].pop(datac_nin[idx])
|
||||||
|
datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx])
|
||||||
|
if nbytes == datac_bytes_per_frame[idx]:
|
||||||
|
rx_total_frames_datac[idx] += 1
|
||||||
|
rx_frames_datac[idx] += 1
|
||||||
|
|
||||||
if rx_frames_datac0 == N_FRAMES_PER_BURST:
|
if rx_frames_datac[idx] == N_FRAMES_PER_BURST:
|
||||||
rx_frames_datac0 = 0
|
rx_frames_datac[idx] = 0
|
||||||
rx_bursts_datac0 = rx_bursts_datac0 + 1
|
rx_bursts_datac[idx] += 1
|
||||||
time_needed_datac0 = time_end_datac0 - time_start_datac0
|
time_needed_datac[idx] = time_end_datac[idx] - time_start_datac[idx]
|
||||||
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
|
print_stats(
|
||||||
|
time_needed_datac[0], time_needed_datac[1], time_needed_datac[2]
|
||||||
|
)
|
||||||
|
|
||||||
while datac1_buffer.nbuffer >= datac1_nin:
|
if (
|
||||||
# demodulate audio
|
rx_bursts_datac[0] == N_BURSTS
|
||||||
time_start_datac1 = time.time()
|
and rx_bursts_datac[1] == N_BURSTS
|
||||||
nbytes = codec2.api.freedv_rawdatarx(datac1_freedv, datac1_bytes_out, datac1_buffer.buffer.ctypes)
|
and rx_bursts_datac[2] == N_BURSTS
|
||||||
time_end_datac1 = time.time()
|
):
|
||||||
datac1_buffer.pop(datac1_nin)
|
|
||||||
datac1_nin = codec2.api.freedv_nin(datac1_freedv)
|
|
||||||
if nbytes == datac1_bytes_per_frame:
|
|
||||||
rx_total_frames_datac1 = rx_total_frames_datac1 + 1
|
|
||||||
rx_frames_datac1 = rx_frames_datac1 + 1
|
|
||||||
|
|
||||||
if rx_frames_datac1 == N_FRAMES_PER_BURST:
|
|
||||||
rx_frames_datac1 = 0
|
|
||||||
rx_bursts_datac1 = rx_bursts_datac1 + 1
|
|
||||||
time_needed_datac1 = time_end_datac1 - time_start_datac1
|
|
||||||
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
|
|
||||||
|
|
||||||
while datac3_buffer.nbuffer >= datac3_nin:
|
|
||||||
# demodulate audio
|
|
||||||
time_start_datac3 = time.time()
|
|
||||||
nbytes = codec2.api.freedv_rawdatarx(datac3_freedv, datac3_bytes_out, datac3_buffer.buffer.ctypes)
|
|
||||||
time_end_datac3 = time.time()
|
|
||||||
datac3_buffer.pop(datac3_nin)
|
|
||||||
datac3_nin = codec2.api.freedv_nin(datac3_freedv)
|
|
||||||
if nbytes == datac3_bytes_per_frame:
|
|
||||||
rx_total_frames_datac3 = rx_total_frames_datac3 + 1
|
|
||||||
rx_frames_datac3 = rx_frames_datac3 + 1
|
|
||||||
|
|
||||||
if rx_frames_datac3 == N_FRAMES_PER_BURST:
|
|
||||||
rx_frames_datac3 = 0
|
|
||||||
rx_bursts_datac3 = rx_bursts_datac3 + 1
|
|
||||||
time_needed_datac3 = time_end_datac3 - time_start_datac3
|
|
||||||
print_stats(time_needed_datac0, time_needed_datac1, time_needed_datac3)
|
|
||||||
|
|
||||||
if rx_bursts_datac0 == N_BURSTS and rx_bursts_datac1 == N_BURSTS and rx_bursts_datac3 == N_BURSTS:
|
|
||||||
receive = False
|
receive = False
|
||||||
|
|
||||||
if nread_exceptions:
|
if nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
nread_exceptions, file=sys.stderr)
|
f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
|
||||||
# INFO IF WE REACHED TIMEOUT
|
"Consider increasing Pyaudio frames_per_buffer...",
|
||||||
if time.time() > timeout:
|
file=sys.stderr,
|
||||||
print(f"TIMEOUT REACHED", file=sys.stderr)
|
)
|
||||||
|
# INFO IF WE REACHED TIMEOUT
|
||||||
|
if time.time() > timeout:
|
||||||
|
print("TIMEOUT REACHED", file=sys.stderr)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"DATAC0: {rx_bursts_datac[0]}/{rx_total_frames_datac[0]} "
|
||||||
|
f"DATAC1: {rx_bursts_datac[1]}/{rx_total_frames_datac[1]} "
|
||||||
|
f"DATAC3: {rx_bursts_datac[2]}/{rx_total_frames_datac[2]}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
|
|
||||||
|
|
||||||
print(f"DATAC0: {rx_bursts_datac0}/{rx_total_frames_datac0} DATAC1: {rx_bursts_datac1}/{rx_total_frames_datac1} DATAC3: {rx_bursts_datac3}/{rx_total_frames_datac3}", file=sys.stderr)
|
|
||||||
|
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
|
||||||
stream_rx.close()
|
stream_rx.close()
|
||||||
p.terminate()
|
p_audio.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description="Simons TEST TNC")
|
||||||
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--audiodev",
|
||||||
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_mm_rx()
|
||||||
|
|
|
@ -2,150 +2,187 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import pyaudio
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import audioop
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0,'..')
|
import time
|
||||||
from tnc import codec2
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pyaudio
|
||||||
|
|
||||||
# GET PARAMETER INPUTS
|
sys.path.insert(0, "..")
|
||||||
parser = argparse.ArgumentParser(description='FreeDATA TEST')
|
from tnc import codec2
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
|
||||||
parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int, help="audio output device number to use")
|
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.LIST:
|
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
for dev in range(0,p.get_device_count()):
|
|
||||||
print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"])
|
|
||||||
quit()
|
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
|
||||||
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000
|
|
||||||
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
|
||||||
|
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
def test_mm_tx():
|
||||||
AUDIO_FRAMES_PER_BUFFER = 2400
|
# AUDIO PARAMETERS
|
||||||
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
AUDIO_FRAMES_PER_BUFFER = 2400
|
||||||
AUDIO_SAMPLE_RATE_TX = 48000
|
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
|
AUDIO_SAMPLE_RATE_TX = 48000
|
||||||
|
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
|
||||||
|
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
args = parse_arguments()
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
# auto search for loopback devices
|
if args.LIST:
|
||||||
|
p_audio = pyaudio.PyAudio()
|
||||||
|
for dev in range(p_audio.get_device_count()):
|
||||||
|
print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"])
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
N_BURSTS = args.N_BURSTS
|
||||||
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
|
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
|
||||||
|
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
||||||
|
|
||||||
|
resampler = codec2.resampler()
|
||||||
|
|
||||||
|
# Data binary string
|
||||||
|
data_out = b"HELLO WORLD!"
|
||||||
|
|
||||||
|
modes = [
|
||||||
|
codec2.api.FREEDV_MODE_DATAC0,
|
||||||
|
codec2.api.FREEDV_MODE_DATAC1,
|
||||||
|
codec2.api.FREEDV_MODE_DATAC3,
|
||||||
|
]
|
||||||
|
|
||||||
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
|
p_audio = pyaudio.PyAudio()
|
||||||
|
# Auto search for loopback devices
|
||||||
if AUDIO_OUTPUT_DEVICE == -2:
|
if AUDIO_OUTPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = [
|
||||||
for dev in range(0,p.get_device_count()):
|
dev
|
||||||
if 'Loopback: PCM' in p.get_device_info_by_index(dev)["name"]:
|
for dev in range(p_audio.get_device_count())
|
||||||
loopback_list.append(dev)
|
if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"]
|
||||||
|
]
|
||||||
|
|
||||||
if len(loopback_list) >= 2:
|
if len(loopback_list) >= 2:
|
||||||
AUDIO_OUTPUT_DEVICE = loopback_list[1] #0 = RX 1 = TX
|
AUDIO_OUTPUT_DEVICE = loopback_list[1] # 0 = RX 1 = TX
|
||||||
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
# pyaudio init
|
# pyaudio init
|
||||||
stream_tx = p.open(format=pyaudio.paInt16,
|
stream_tx = p_audio.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=AUDIO_SAMPLE_RATE_TX,
|
rate=AUDIO_SAMPLE_RATE_TX,
|
||||||
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, #n_nom_modem_samples
|
frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples
|
||||||
output=True,
|
output=True,
|
||||||
output_device_index=AUDIO_OUTPUT_DEVICE,
|
output_device_index=AUDIO_OUTPUT_DEVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
resampler = codec2.resampler()
|
for mode in modes:
|
||||||
modes = [codec2.api.FREEDV_MODE_DATAC0, codec2.api.FREEDV_MODE_DATAC1, codec2.api.FREEDV_MODE_DATAC3]
|
freedv = ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p)
|
||||||
for m in modes:
|
|
||||||
|
|
||||||
freedv = cast(codec2.api.freedv_open(m), c_void_p)
|
|
||||||
|
|
||||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||||
mod_out = create_string_buffer(2*n_tx_modem_samples)
|
mod_out = ctypes.create_string_buffer(2 * n_tx_modem_samples)
|
||||||
|
|
||||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
|
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||||
mod_out_preamble = create_string_buffer(2*n_tx_preamble_modem_samples)
|
freedv
|
||||||
|
)
|
||||||
|
mod_out_preamble = ctypes.create_string_buffer(2 * n_tx_preamble_modem_samples)
|
||||||
|
|
||||||
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
n_tx_postamble_modem_samples = (
|
||||||
mod_out_postamble = create_string_buffer(2*n_tx_postamble_modem_samples)
|
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||||
|
)
|
||||||
|
mod_out_postamble = ctypes.create_string_buffer(
|
||||||
|
2 * n_tx_postamble_modem_samples
|
||||||
|
)
|
||||||
|
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
payload_per_frame = bytes_per_frame - 2
|
payload_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
|
|
||||||
# data binary string
|
|
||||||
data_out = b'HELLO WORLD!'
|
|
||||||
|
|
||||||
buffer = bytearray(payload_per_frame)
|
buffer = bytearray(payload_per_frame)
|
||||||
# set buffersize to length of data which will be send
|
# Set buffersize to length of data which will be send
|
||||||
buffer[:len(data_out)] = data_out
|
buffer[: len(data_out)] = data_out
|
||||||
|
|
||||||
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)) # generate CRC16
|
# Generate CRC16
|
||||||
# convert crc to 2 byte hex string
|
crc = ctypes.c_ushort(
|
||||||
crc = crc.value.to_bytes(2, byteorder='big')
|
codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame)
|
||||||
buffer += crc # append crc16 to buffer
|
)
|
||||||
|
# Convert CRC to 2 byte hex string
|
||||||
|
crc = crc.value.to_bytes(2, byteorder="big")
|
||||||
|
buffer += crc # Append crc16 to buffer
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
|
|
||||||
for i in range(1,N_BURSTS+1):
|
for brst in range(1, N_BURSTS + 1):
|
||||||
|
# Write preamble to txbuffer
|
||||||
# write preamble to txbuffer
|
|
||||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
txbuffer = bytes(mod_out_preamble)
|
txbuffer = bytes(mod_out_preamble)
|
||||||
|
|
||||||
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
# Create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
||||||
for n in range(1,N_FRAMES_PER_BURST+1):
|
for frm in range(1, N_FRAMES_PER_BURST + 1):
|
||||||
|
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer
|
# Modulate DATA and save it into mod_out pointer
|
||||||
|
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||||
|
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
print(f"TX BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr)
|
print(
|
||||||
|
f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# append postamble to txbuffer
|
# Append postamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||||
txbuffer += bytes(mod_out_postamble)
|
txbuffer += bytes(mod_out_postamble)
|
||||||
|
|
||||||
# append a delay between bursts as audio silence
|
# Append a delay between bursts as audio silence
|
||||||
samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS)
|
samples_delay = int(MODEM_SAMPLE_RATE * DELAY_BETWEEN_BURSTS)
|
||||||
mod_out_silence = create_string_buffer(samples_delay*2)
|
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
|
||||||
txbuffer += bytes(mod_out_silence)
|
txbuffer += bytes(mod_out_silence)
|
||||||
|
|
||||||
# resample up to 48k (resampler works on np.int16)
|
# Resample up to 48k (resampler works on np.int16)
|
||||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
audio_buffer = np.frombuffer(txbuffer, dtype=np.int16)
|
||||||
txbuffer_48k = resampler.resample8_to_48(x)
|
txbuffer_48k = resampler.resample8_to_48(audio_buffer)
|
||||||
|
|
||||||
# check if we want to use an audio device or stdout
|
# Check if we want to use an audio device or stdout
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
stream_tx.write(txbuffer_48k.tobytes())
|
stream_tx.write(txbuffer_48k.tobytes())
|
||||||
else:
|
else:
|
||||||
# this test needs a lot of time, so we are having a look at times...
|
# This test needs a lot of time, so we are having a look at times...
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
|
|
||||||
# print data to terminal for piping the output to other programs
|
# Print data to terminal for piping the output to other programs
|
||||||
sys.stdout.buffer.write(txbuffer_48k)
|
sys.stdout.buffer.write(txbuffer_48k)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
# and at least print the needed time to see which time we needed
|
# and at least print the needed time to see which time we needed
|
||||||
timeneeded = time.time()-starttime
|
timeneeded = time.time() - starttime
|
||||||
#print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr)
|
# print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr)
|
||||||
|
|
||||||
|
# and at last check if we had an opened pyaudio instance and close it
|
||||||
# and at last check if we had an opened pyaudio instance and close it
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
|
||||||
time.sleep(stream_tx.get_output_latency())
|
time.sleep(stream_tx.get_output_latency())
|
||||||
stream_tx.stop_stream()
|
stream_tx.stop_stream()
|
||||||
stream_tx.close()
|
stream_tx.close()
|
||||||
p.terminate()
|
p_audio.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
# GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description="FreeDATA TEST")
|
||||||
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int
|
||||||
|
)
|
||||||
|
parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--audiodev",
|
||||||
|
dest="AUDIO_OUTPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio output device number to use",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_mm_tx()
|
||||||
|
|
|
@ -3,37 +3,46 @@
|
||||||
#
|
#
|
||||||
# Throw away test program to help understand the care and feeding of PyAudio
|
# Throw away test program to help understand the care and feeding of PyAudio
|
||||||
|
|
||||||
import pyaudio
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pyaudio
|
||||||
|
|
||||||
CHUNK = 1024
|
CHUNK = 1024
|
||||||
FS48 = 48000
|
FS48 = 48000
|
||||||
FTEST = 800
|
FTEST = 800
|
||||||
AMP = 16000
|
AMP = 16000
|
||||||
|
|
||||||
# 1. play sine wave out of default sound device
|
|
||||||
|
|
||||||
p = pyaudio.PyAudio()
|
def test_pa():
|
||||||
stream = p.open(format=pyaudio.paInt16,
|
# 1. play sine wave out of default sound device
|
||||||
|
|
||||||
|
p_audio = pyaudio.PyAudio()
|
||||||
|
stream = p_audio.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
rate=FS48,
|
rate=FS48,
|
||||||
frames_per_buffer=CHUNK,
|
frames_per_buffer=CHUNK,
|
||||||
output=True
|
output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
f48 = open("out48.raw", mode='wb')
|
with open("out48.raw", mode="wb") as f48:
|
||||||
t = 0;
|
temp = 0
|
||||||
for f in range(50):
|
for _ in range(50):
|
||||||
sine_48k = (AMP*np.cos(2*np.pi*np.arange(t,t+CHUNK)*FTEST/FS48)).astype(np.int16)
|
sine_48k = (
|
||||||
t += CHUNK
|
AMP * np.cos(2 * np.pi * np.arange(temp, temp + CHUNK) * FTEST / FS48)
|
||||||
|
).astype(np.int16)
|
||||||
|
temp += CHUNK
|
||||||
sine_48k.tofile(f48)
|
sine_48k.tofile(f48)
|
||||||
stream.write(sine_48k.tobytes())
|
stream.write(sine_48k.tobytes())
|
||||||
sil_48k = np.zeros(CHUNK, dtype=np.int16)
|
sil_48k = np.zeros(CHUNK, dtype=np.int16)
|
||||||
for f in range(50):
|
|
||||||
|
for _ in range(50):
|
||||||
sil_48k.tofile(f48)
|
sil_48k.tofile(f48)
|
||||||
stream.write(sil_48k)
|
stream.write(sil_48k)
|
||||||
|
|
||||||
stream.stop_stream()
|
stream.stop_stream()
|
||||||
stream.close()
|
stream.close()
|
||||||
p.terminate()
|
p_audio.terminate()
|
||||||
f48.close()
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_pa()
|
||||||
|
|
120
test/test_resample_48_8.py
Normal file
120
test/test_resample_48_8.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Unit test for FreeDV API resampler functions, from
|
||||||
|
# codec2/unittest/t48_8_short.c - generate a sine wave at 8 KHz,
|
||||||
|
# upsample to 48 kHz, add an interferer, then downsample back to 8 kHz
|
||||||
|
#
|
||||||
|
# You can listen to the output files with:
|
||||||
|
#
|
||||||
|
# aplay -f S16_LE in8.raw
|
||||||
|
# aplay -r 48000 -f S16_LE out48.raw
|
||||||
|
# aplay -f S16_LE out8.raw
|
||||||
|
#
|
||||||
|
# They should sound like clean sine waves
|
||||||
|
|
||||||
|
# pylint: disable=global-statement, invalid-name, unused-import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import codec2
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# dig some constants out
|
||||||
|
FDMDV_OS_48 = codec2.api.FDMDV_OS_48
|
||||||
|
FDMDV_OS_TAPS_48K = codec2.api.FDMDV_OS_TAPS_48K
|
||||||
|
FDMDV_OS_TAPS_48_8K = codec2.api.FDMDV_OS_TAPS_48_8K
|
||||||
|
|
||||||
|
N8 = 180 # processing buffer size at 8 kHz
|
||||||
|
N48 = N8 * FDMDV_OS_48 # processing buffer size at 48 kHz
|
||||||
|
MEM8 = FDMDV_OS_TAPS_48_8K # 8kHz signal filter memory
|
||||||
|
MEM48 = FDMDV_OS_TAPS_48K # 48kHz signal filter memory
|
||||||
|
FRAMES = 50 # number of frames to test
|
||||||
|
FS8 = 8000
|
||||||
|
FS48 = 48000
|
||||||
|
AMP = 16000 # sine wave amplitude
|
||||||
|
FTEST8 = 800 # input test frequency at FS=8kHz
|
||||||
|
FINTER48 = 10000 # interferer frequency at FS=48kHz
|
||||||
|
|
||||||
|
# Due to the design of these resamplers, the processing buffer (at 8kHz)
|
||||||
|
# must be an integer multiple of oversampling ratio
|
||||||
|
assert N8 % FDMDV_OS_48 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_resampler():
|
||||||
|
"""
|
||||||
|
Test for the codec2 audio resampling routine
|
||||||
|
"""
|
||||||
|
# time indexes, we advance every frame
|
||||||
|
t = 0
|
||||||
|
t1 = 0
|
||||||
|
|
||||||
|
# output files to listen to/evaluate result
|
||||||
|
with open("in8.raw", mode="wb") as fin8:
|
||||||
|
with open("out48.raw", mode="wb") as f48:
|
||||||
|
with open("out8.raw", mode="wb") as fout8:
|
||||||
|
resampler = codec2.resampler()
|
||||||
|
|
||||||
|
# Generate FRAMES of a sine wave
|
||||||
|
for _ in range(FRAMES):
|
||||||
|
# Primary sine wave, which the down-sampling filter should retain.
|
||||||
|
sine_in8k = (
|
||||||
|
AMP * np.cos(2 * np.pi * np.arange(t, t + N8) * FTEST8 / FS8)
|
||||||
|
).astype(np.int16)
|
||||||
|
t += N8
|
||||||
|
sine_in8k.tofile(fin8)
|
||||||
|
|
||||||
|
sine_out48k = resampler.resample8_to_48(sine_in8k)
|
||||||
|
sine_out48k.tofile(f48)
|
||||||
|
|
||||||
|
# Add an interfering sine wave, which the down-sampling filter should (mostly) remove
|
||||||
|
sine_in48k = (
|
||||||
|
sine_out48k
|
||||||
|
+ (AMP / 2)
|
||||||
|
* np.cos(2 * np.pi * np.arange(t1, t1 + N48) * FINTER48 / FS48)
|
||||||
|
).astype(np.int16)
|
||||||
|
t1 += N48
|
||||||
|
|
||||||
|
sine_out8k = resampler.resample48_to_8(sine_in48k)
|
||||||
|
sine_out8k.tofile(fout8)
|
||||||
|
|
||||||
|
# os.unlink("out48.raw")
|
||||||
|
|
||||||
|
# Automated test evaluation --------------------------------------------
|
||||||
|
|
||||||
|
# The input and output signals will not be time aligned due to the filter
|
||||||
|
# delays, so compare the magnitude spectrum
|
||||||
|
|
||||||
|
# Read the raw audio files
|
||||||
|
in8k = np.fromfile("in8.raw", dtype=np.int16)
|
||||||
|
out8k = np.fromfile("out8.raw", dtype=np.int16)
|
||||||
|
assert len(in8k) == len(out8k)
|
||||||
|
# os.unlink("in8.raw")
|
||||||
|
# os.unlink("out8.raw")
|
||||||
|
|
||||||
|
# Apply hanning filter to raw input data samples
|
||||||
|
h = np.hanning(len(in8k))
|
||||||
|
S1 = np.abs(np.fft.fft(in8k * h))
|
||||||
|
S2 = np.abs(np.fft.fft(out8k * h))
|
||||||
|
|
||||||
|
# Calculate the ratio between signal and noise (error energy).
|
||||||
|
error = S1 - S2
|
||||||
|
error_energy = np.dot(error, error)
|
||||||
|
ratio = error_energy / np.dot(S1, S1)
|
||||||
|
ratio_dB = 10 * np.log10(ratio)
|
||||||
|
|
||||||
|
# Establish -40.0 as the noise ratio ceiling
|
||||||
|
threshdB = -40.0
|
||||||
|
print(f"ratio_dB: {ratio_dB:4.2}" % (ratio_dB))
|
||||||
|
assert ratio_dB < threshdB
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("PASS")
|
||||||
|
else:
|
||||||
|
print("FAIL")
|
237
test/test_rx.py
237
test/test_rx.py
|
@ -6,35 +6,22 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import sounddevice as sd
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
sys.path.insert(0,'..')
|
import sounddevice as sd
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
from tnc import codec2
|
from tnc import codec2
|
||||||
|
|
||||||
|
|
||||||
#--------------------------------------------GET PARAMETER INPUTS
|
def test_rx():
|
||||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
args = parse_arguments()
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_INPUT_DEVICE", default=-1, type=int,
|
|
||||||
help="audio device number to use, use -2 to automatically select a loopback device")
|
|
||||||
parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true")
|
|
||||||
parser.add_argument('--timeout', dest="TIMEOUT", default=10, type=int, help="Timeout (seconds) before test ends")
|
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
if args.LIST:
|
||||||
|
|
||||||
if args.LIST:
|
|
||||||
|
|
||||||
devices = sd.query_devices(device=None, kind=None)
|
devices = sd.query_devices(device=None, kind=None)
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -42,110 +29,115 @@ if args.LIST:
|
||||||
print(f"{index} {device['name']}")
|
print(f"{index} {device['name']}")
|
||||||
index += 1
|
index += 1
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
N_BURSTS = args.N_BURSTS
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
|
AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE
|
||||||
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
||||||
DEBUGGING_MODE = args.DEBUGGING_MODE
|
DEBUGGING_MODE = args.DEBUGGING_MODE
|
||||||
TIMEOUT = args.TIMEOUT
|
MAX_TIME = args.TIMEOUT
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
AUDIO_FRAMES_PER_BUFFER = 2400*2 # <- consider increasing if you get nread_exceptions > 0
|
# v-- consider increasing if you get nread_exceptions > 0
|
||||||
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
AUDIO_FRAMES_PER_BUFFER = 2400 * 2
|
||||||
AUDIO_SAMPLE_RATE_RX = 48000
|
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
|
AUDIO_SAMPLE_RATE_RX = 48000
|
||||||
|
|
||||||
# make sure our resampler will work
|
# make sure our resampler will work
|
||||||
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if AUDIO_INPUT_DEVICE == -2:
|
if AUDIO_INPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
|
|
||||||
devices = sd.query_devices(device=None, kind=None)
|
devices = sd.query_devices(device=None, kind=None)
|
||||||
index = 0
|
|
||||||
|
|
||||||
for device in devices:
|
for index, device in enumerate(devices):
|
||||||
if 'Loopback: PCM' in device['name']:
|
if "Loopback: PCM" in device["name"]:
|
||||||
print(index)
|
print(index)
|
||||||
loopback_list.append(index)
|
loopback_list.append(index)
|
||||||
index += 1
|
|
||||||
|
|
||||||
if len(loopback_list) >= 1:
|
if loopback_list:
|
||||||
AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX 1 = TX
|
# 0 = RX 1 = TX
|
||||||
|
AUDIO_INPUT_DEVICE = loopback_list[0]
|
||||||
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
print("not enough audio loopback devices ready...")
|
print("not enough audio loopback devices ready...")
|
||||||
print("you should wait about 30 seconds...")
|
print("you should wait about 30 seconds...")
|
||||||
|
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
quit()
|
sys.exit()
|
||||||
print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE}", file=sys.stderr)
|
print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE}", file=sys.stderr)
|
||||||
|
|
||||||
# audio stream init
|
# audio stream init
|
||||||
stream_rx = sd.RawStream(channels=1, dtype='int16', device=AUDIO_INPUT_DEVICE, samplerate = AUDIO_SAMPLE_RATE_RX, blocksize=4800)
|
stream_rx = sd.RawStream(
|
||||||
|
channels=1,
|
||||||
|
dtype="int16",
|
||||||
|
device=AUDIO_INPUT_DEVICE,
|
||||||
|
samplerate=AUDIO_SAMPLE_RATE_RX,
|
||||||
|
blocksize=4800,
|
||||||
|
)
|
||||||
stream_rx.start()
|
stream_rx.start()
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
|
# DATA CHANNEL INITIALISATION
|
||||||
|
|
||||||
# DATA CHANNEL INITIALISATION
|
# open codec2 instance
|
||||||
|
freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# open codec2 instance
|
# get number of bytes per frame for mode
|
||||||
freedv = cast(codec2.api.freedv_open(MODE), c_void_p)
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
|
payload_bytes_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(freedv)
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8)
|
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||||
payload_bytes_per_frame = bytes_per_frame -2
|
|
||||||
|
|
||||||
n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(freedv)
|
codec2.api.freedv_set_frames_per_burst(freedv, N_FRAMES_PER_BURST)
|
||||||
bytes_out = create_string_buffer(bytes_per_frame)
|
|
||||||
|
|
||||||
codec2.api.freedv_set_frames_per_burst(freedv,N_FRAMES_PER_BURST)
|
total_n_bytes = 0
|
||||||
|
rx_total_frames = 0
|
||||||
|
rx_frames = 0
|
||||||
|
rx_bursts = 0
|
||||||
|
rx_errors = 0
|
||||||
|
nread_exceptions = 0
|
||||||
|
timeout = time.time() + MAX_TIME
|
||||||
|
receive = True
|
||||||
|
audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
|
resampler = codec2.resampler()
|
||||||
|
|
||||||
total_n_bytes = 0
|
# time meassurement
|
||||||
rx_total_frames = 0
|
time_start = 0
|
||||||
rx_frames = 0
|
time_end = 0
|
||||||
rx_bursts = 0
|
|
||||||
rx_errors = 0
|
|
||||||
nread_exceptions = 0
|
|
||||||
timeout = time.time() + TIMEOUT
|
|
||||||
receive = True
|
|
||||||
audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER*2)
|
|
||||||
resampler = codec2.resampler()
|
|
||||||
|
|
||||||
# time meassurement
|
# Copy received 48 kHz to a file. Listen to this file with:
|
||||||
time_start = 0
|
# aplay -r 48000 -f S16_LE rx48.raw
|
||||||
time_end = 0
|
# Corruption of this file is a good way to detect audio card issues
|
||||||
|
frx = open("rx48.raw", mode="wb")
|
||||||
|
|
||||||
# Copy received 48 kHz to a file. Listen to this file with:
|
# initial number of samples we need
|
||||||
# aplay -r 48000 -f S16_LE rx48.raw
|
nin = codec2.api.freedv_nin(freedv)
|
||||||
# Corruption of this file is a good way to detect audio card issues
|
while receive and time.time() < timeout:
|
||||||
frx = open("rx48.raw", mode='wb')
|
|
||||||
|
|
||||||
# initial number of samples we need
|
|
||||||
nin = codec2.api.freedv_nin(freedv)
|
|
||||||
while receive and time.time() < timeout:
|
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
try:
|
try:
|
||||||
#data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
|
# data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
|
||||||
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER)
|
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
print(err, file=sys.stderr)
|
print(err, file=sys.stderr)
|
||||||
#if str(err).find("Input overflowed") != -1:
|
# if str(err).find("Input overflowed") != -1:
|
||||||
# nread_exceptions += 1
|
# nread_exceptions += 1
|
||||||
#if str(err).find("Stream closed") != -1:
|
# if str(err).find("Stream closed") != -1:
|
||||||
# print("Ending...")
|
# print("Ending...")
|
||||||
# receive = False
|
# receive = False
|
||||||
else:
|
else:
|
||||||
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER*2)
|
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
|
||||||
|
|
||||||
# insert samples in buffer
|
# insert samples in buffer
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
#print(x)
|
# print(x)
|
||||||
#x = data_in48k
|
# x = data_in48k
|
||||||
x.tofile(frx)
|
x.tofile(frx)
|
||||||
if len(x) != AUDIO_FRAMES_PER_BUFFER:
|
if len(x) != AUDIO_FRAMES_PER_BUFFER:
|
||||||
receive = False
|
receive = False
|
||||||
|
@ -157,7 +149,9 @@ while receive and time.time() < timeout:
|
||||||
# start time measurement
|
# start time measurement
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
# demodulate audio
|
# demodulate audio
|
||||||
nbytes = codec2.api.freedv_rawdatarx(freedv, bytes_out, audio_buffer.buffer.ctypes)
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
|
freedv, bytes_out, audio_buffer.buffer.ctypes
|
||||||
|
)
|
||||||
time_end = time.time()
|
time_end = time.time()
|
||||||
|
|
||||||
audio_buffer.pop(nin)
|
audio_buffer.pop(nin)
|
||||||
|
@ -172,19 +166,22 @@ while receive and time.time() < timeout:
|
||||||
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
rx_status = codec2.api.rx_sync_flags_to_text[rx_status]
|
||||||
time_needed = time_end - time_start
|
time_needed = time_end - time_start
|
||||||
|
|
||||||
print("nin: %5d rx_status: %4s naudio_buffer: %4d time: %4s" % \
|
print(
|
||||||
(nin,rx_status,audio_buffer.nbuffer, time_needed), file=sys.stderr)
|
f"nin: {nin:5d} rx_status: {rx_status:4s} "
|
||||||
|
f"naudio_buffer: {audio_buffer.nbuffer:4d} time: {time_needed:4f}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
if nbytes:
|
if nbytes:
|
||||||
total_n_bytes = total_n_bytes + nbytes
|
total_n_bytes += nbytes
|
||||||
|
|
||||||
if nbytes == bytes_per_frame:
|
if nbytes == bytes_per_frame:
|
||||||
rx_total_frames = rx_total_frames + 1
|
rx_total_frames += 1
|
||||||
rx_frames = rx_frames + 1
|
rx_frames += 1
|
||||||
|
|
||||||
if rx_frames == N_FRAMES_PER_BURST:
|
if rx_frames == N_FRAMES_PER_BURST:
|
||||||
rx_frames = 0
|
rx_frames = 0
|
||||||
rx_bursts = rx_bursts + 1
|
rx_bursts += 1
|
||||||
|
|
||||||
if rx_bursts == N_BURSTS:
|
if rx_bursts == N_BURSTS:
|
||||||
receive = False
|
receive = False
|
||||||
|
@ -192,15 +189,59 @@ while receive and time.time() < timeout:
|
||||||
if time.time() >= timeout:
|
if time.time() >= timeout:
|
||||||
print("TIMEOUT REACHED")
|
print("TIMEOUT REACHED")
|
||||||
|
|
||||||
if nread_exceptions:
|
if nread_exceptions:
|
||||||
print("nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." % \
|
print(
|
||||||
nread_exceptions, file=sys.stderr)
|
f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
|
||||||
print(f"RECEIVED BURSTS: {rx_bursts} RECEIVED FRAMES: {rx_total_frames} RX_ERRORS: {rx_errors}", file=sys.stderr)
|
"Consider increasing Pyaudio frames_per_buffer...",
|
||||||
frx.close()
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"RECEIVED BURSTS: {rx_bursts} "
|
||||||
|
f"RECEIVED FRAMES: {rx_total_frames} "
|
||||||
|
f"RX_ERRORS: {rx_errors}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
frx.close()
|
||||||
|
|
||||||
|
# and at last check if we had an opened audio instance and close it
|
||||||
# and at last check if we had an opened audio instance and close it
|
if AUDIO_INPUT_DEVICE != -1:
|
||||||
if AUDIO_INPUT_DEVICE != -1:
|
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description="Simons TEST TNC")
|
||||||
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--audiodev",
|
||||||
|
dest="AUDIO_INPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio device number to use, use -2 to automatically select a loopback device",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
dest="TIMEOUT",
|
||||||
|
default=10,
|
||||||
|
type=int,
|
||||||
|
help="Timeout (seconds) before test ends",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_rx()
|
||||||
|
|
54
test/test_tnc.py
Executable file
54
test/test_tnc.py
Executable file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# pylint: disable=wrong-import-position
|
||||||
|
sys.path.insert(0, "..")
|
||||||
|
sys.path.insert(0, "../tnc")
|
||||||
|
sys.path.insert(0, "test")
|
||||||
|
import test_tnc_IRS as irs
|
||||||
|
import test_tnc_ISS as iss
|
||||||
|
|
||||||
|
|
||||||
|
# These do not update static.INFO.
|
||||||
|
# "CONNECT", "SEND_TEST_FRAME"
|
||||||
|
@pytest.mark.parametrize("command", ["CQ", "PING", "BEACON"])
|
||||||
|
def test_tnc(command):
|
||||||
|
|
||||||
|
iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command])
|
||||||
|
irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command])
|
||||||
|
# print("Starting threads.")
|
||||||
|
iss_proc.start()
|
||||||
|
irs_proc.start()
|
||||||
|
|
||||||
|
time.sleep(12)
|
||||||
|
|
||||||
|
# print("Terminating threads.")
|
||||||
|
irs_proc.terminate()
|
||||||
|
iss_proc.terminate()
|
||||||
|
irs_proc.join()
|
||||||
|
iss_proc.join()
|
||||||
|
|
||||||
|
for idx in range(2):
|
||||||
|
try:
|
||||||
|
os.unlink(f"/tmp/hfchannel{idx+1}")
|
||||||
|
except FileNotFoundError as fnfe:
|
||||||
|
print(f"Unlinking pipe: {fnfe}")
|
||||||
|
|
||||||
|
assert iss_proc.exitcode == 0, f"Transmit side failed test. {iss_proc}"
|
||||||
|
assert irs_proc.exitcode == 0, f"Receive side failed test. {irs_proc}"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-s", "-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
81
test/test_tnc_IRS.py
Normal file
81
test/test_tnc_IRS.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Wed Dec 23 07:04:24 2020
|
||||||
|
|
||||||
|
@author: DJ2LS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
|
sys.path.insert(0, "../tnc")
|
||||||
|
import data_handler
|
||||||
|
import helpers
|
||||||
|
import modem
|
||||||
|
import static
|
||||||
|
|
||||||
|
IRS_original_arq_cleanup = object
|
||||||
|
MESSAGE: str
|
||||||
|
|
||||||
|
|
||||||
|
def irs_arq_cleanup():
|
||||||
|
"""Replacement for modem.arq_cleanup to detect when to exit process."""
|
||||||
|
if "TRANSMISSION;STOPPED" in static.INFO:
|
||||||
|
print(f"{static.INFO=}")
|
||||||
|
time.sleep(2)
|
||||||
|
# sys.exit does not terminate threads.
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
if f"{MESSAGE};RECEIVING" not in static.INFO:
|
||||||
|
print(f"{MESSAGE} was not received.")
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
os._exit(0)
|
||||||
|
IRS_original_arq_cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
def t_arq_irs(*args):
|
||||||
|
# pylint: disable=global-statement
|
||||||
|
global IRS_original_arq_cleanup, MESSAGE
|
||||||
|
|
||||||
|
MESSAGE = args[0]
|
||||||
|
|
||||||
|
# enable testmode
|
||||||
|
data_handler.TESTMODE = True
|
||||||
|
modem.TESTMODE = True
|
||||||
|
modem.RXCHANNEL = "/tmp/hfchannel2"
|
||||||
|
modem.TXCHANNEL = "/tmp/hfchannel1"
|
||||||
|
static.HAMLIB_RADIOCONTROL = "disabled"
|
||||||
|
static.RESPOND_TO_CQ = True
|
||||||
|
|
||||||
|
mycallsign = bytes("DN2LS-2", "utf-8")
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||||
|
static.MYGRID = bytes("AA12aa", "utf-8")
|
||||||
|
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
|
||||||
|
# start data handler
|
||||||
|
tnc = data_handler.DATA()
|
||||||
|
|
||||||
|
# Inject a way to exit the TNC infinite loop
|
||||||
|
IRS_original_arq_cleanup = tnc.arq_cleanup
|
||||||
|
tnc.arq_cleanup = irs_arq_cleanup
|
||||||
|
|
||||||
|
# start modem
|
||||||
|
t_modem = modem.RF()
|
||||||
|
|
||||||
|
# Set timeout
|
||||||
|
timeout = time.time() + 10
|
||||||
|
|
||||||
|
while time.time() < timeout:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
assert not "TIMEOUT!"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("This cannot be run as an application.")
|
||||||
|
sys.exit(1)
|
118
test/test_tnc_ISS.py
Normal file
118
test/test_tnc_ISS.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Wed Dec 23 07:04:24 2020
|
||||||
|
|
||||||
|
@author: DJ2LS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
sys.path.insert(0, "..")
|
||||||
|
sys.path.insert(0, "../tnc")
|
||||||
|
import data_handler
|
||||||
|
import helpers
|
||||||
|
import modem
|
||||||
|
import static
|
||||||
|
|
||||||
|
ISS_original_arq_cleanup = object
|
||||||
|
MESSAGE: str
|
||||||
|
|
||||||
|
|
||||||
|
def iss_arq_cleanup():
|
||||||
|
"""Replacement for modem.arq_cleanup to detect when to exit process."""
|
||||||
|
if "TRANSMISSION;STOPPED" in static.INFO:
|
||||||
|
print(f"{static.INFO=}")
|
||||||
|
time.sleep(1)
|
||||||
|
# sys.exit does not terminate threads.
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
if f"{MESSAGE};SENDING" not in static.INFO:
|
||||||
|
print(f"{MESSAGE} was not sent.")
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
os._exit(0)
|
||||||
|
ISS_original_arq_cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
def t_arq_iss(*args):
|
||||||
|
# pylint: disable=global-statement
|
||||||
|
global ISS_original_arq_cleanup, MESSAGE
|
||||||
|
|
||||||
|
MESSAGE = args[0]
|
||||||
|
|
||||||
|
# enable testmode
|
||||||
|
data_handler.TESTMODE = True
|
||||||
|
modem.TESTMODE = True
|
||||||
|
modem.RXCHANNEL = "/tmp/hfchannel1"
|
||||||
|
modem.TXCHANNEL = "/tmp/hfchannel2"
|
||||||
|
static.HAMLIB_RADIOCONTROL = "disabled"
|
||||||
|
|
||||||
|
mycallsign = bytes("DJ2LS-2", "utf-8")
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||||
|
static.MYGRID = bytes("AA12aa", "utf-8")
|
||||||
|
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
|
||||||
|
dxcallsign = b"DN2LS-0"
|
||||||
|
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||||
|
static.DXCALLSIGN = dxcallsign
|
||||||
|
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
||||||
|
|
||||||
|
bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}'
|
||||||
|
|
||||||
|
# start data handler
|
||||||
|
tnc = data_handler.DATA()
|
||||||
|
|
||||||
|
# Inject a way to exit the TNC infinite loop
|
||||||
|
ISS_original_arq_cleanup = tnc.arq_cleanup
|
||||||
|
tnc.arq_cleanup = iss_arq_cleanup
|
||||||
|
|
||||||
|
# start modem
|
||||||
|
t_modem = modem.RF()
|
||||||
|
|
||||||
|
# mode = codec2.freedv_get_mode_value_by_name(FREEDV_MODE)
|
||||||
|
# n_frames_per_burst = N_FRAMES_PER_BURST
|
||||||
|
|
||||||
|
# add command to data qeue
|
||||||
|
"""
|
||||||
|
elif data[0] == 'ARQ_RAW':
|
||||||
|
# [0] ARQ_RAW
|
||||||
|
# [1] DATA_OUT bytes
|
||||||
|
# [2] MODE int
|
||||||
|
# [3] N_FRAMES_PER_BURST int
|
||||||
|
# [4] self.transmission_uuid str
|
||||||
|
# [5] mycallsign with ssid
|
||||||
|
"""
|
||||||
|
# data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', bytes_out, 255, n_frames_per_burst, '123', b'DJ2LS-0'])
|
||||||
|
|
||||||
|
# for _ in range(4):
|
||||||
|
if MESSAGE in ["BEACON"]:
|
||||||
|
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE, 5, True])
|
||||||
|
elif MESSAGE in ["PING", "CONNECT"]:
|
||||||
|
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE, dxcallsign])
|
||||||
|
else:
|
||||||
|
data_handler.DATA_QUEUE_TRANSMIT.put([MESSAGE])
|
||||||
|
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
# for i in range(4):
|
||||||
|
# data_handler.DATA_QUEUE_TRANSMIT.put(['PING', b'DN2LS-2'])
|
||||||
|
|
||||||
|
data_handler.DATA_QUEUE_TRANSMIT.put(["STOP"])
|
||||||
|
|
||||||
|
# Set timeout
|
||||||
|
timeout = time.time() + 10
|
||||||
|
|
||||||
|
while time.time() < timeout:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
assert not "TIMEOUT!"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("This cannot be run as an application.")
|
||||||
|
sys.exit(-1)
|
225
test/test_tnc_states.py
Normal file
225
test/test_tnc_states.py
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
"""
|
||||||
|
Tests for the FreeDATA TNC state machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# pylint: disable=wrong-import-position
|
||||||
|
sys.path.insert(0, "..")
|
||||||
|
sys.path.insert(0, "../tnc")
|
||||||
|
import data_handler
|
||||||
|
import helpers
|
||||||
|
import static
|
||||||
|
|
||||||
|
|
||||||
|
def print_frame(data: bytearray):
|
||||||
|
"""
|
||||||
|
Pretty-print the provided frame.
|
||||||
|
|
||||||
|
:param data: Frame to be output
|
||||||
|
:type data: bytearray
|
||||||
|
"""
|
||||||
|
print(f"Type : {int(data[0])}")
|
||||||
|
print(f"DXCRC : {bytes(data[1:4])}")
|
||||||
|
print(f"CallCRC: {bytes(data[4:7])}")
|
||||||
|
print(f"Call : {helpers.bytes_to_callsign(data[7:13])}")
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the requested frame.
|
||||||
|
|
||||||
|
:param frame_type: The numerical type of the desired frame.
|
||||||
|
:type frame_type: int
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
||||||
|
mycallsign_crc = helpers.get_crc_24(mycallsign)
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
|
frame = bytearray(14)
|
||||||
|
frame[:1] = bytes([frame_type])
|
||||||
|
frame[1:4] = dxcallsign_crc
|
||||||
|
frame[4:7] = mycallsign_crc
|
||||||
|
frame[7:13] = mycallsign_bytes
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the session_close frame.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
return t_create_frame(223, mycall, dxcall)
|
||||||
|
|
||||||
|
|
||||||
|
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
|
||||||
|
"""
|
||||||
|
Generate the create_session frame.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
return t_create_frame(221, mycall, dxcall)
|
||||||
|
|
||||||
|
|
||||||
|
def t_tsh_dummy():
|
||||||
|
"""Replacement function for transmit_session_heartbeat"""
|
||||||
|
print("In transmit_session_heartbeat")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "M4AWQ-4"])
|
||||||
|
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
|
||||||
|
def test_valid_disconnect(mycall: str, dxcall: str):
|
||||||
|
"""
|
||||||
|
Execute test to validate that receiving a session open frame sets the correct machine
|
||||||
|
state.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
# Set the SSIDs we'll use for this test.
|
||||||
|
static.SSID_LIST = [0, 1, 2, 3, 4]
|
||||||
|
|
||||||
|
# Setup the static parameters for the connection.
|
||||||
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
||||||
|
static.MYCALLSIGN = mycallsign
|
||||||
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign)
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
static.DXCALLSIGN = dxcallsign
|
||||||
|
static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
|
# Create the TNC
|
||||||
|
tnc = data_handler.DATA()
|
||||||
|
|
||||||
|
# Replace the heartbeat transmit routine with our own, a No-Op.
|
||||||
|
tnc.transmit_session_heartbeat = t_tsh_dummy
|
||||||
|
|
||||||
|
# Create packet to be 'received' by this station.
|
||||||
|
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
|
||||||
|
print_frame(create_frame)
|
||||||
|
tnc.received_session_opener(create_frame)
|
||||||
|
|
||||||
|
assert static.ARQ_SESSION is True
|
||||||
|
assert static.TNC_STATE == "BUSY"
|
||||||
|
assert static.ARQ_SESSION_STATE == "connecting"
|
||||||
|
|
||||||
|
# Create packet to be 'received' by this station.
|
||||||
|
close_frame = t_create_session_close(mycall=dxcall, dxcall=mycall)
|
||||||
|
print_frame(close_frame)
|
||||||
|
tnc.received_session_close(close_frame)
|
||||||
|
|
||||||
|
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes
|
||||||
|
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes
|
||||||
|
|
||||||
|
assert static.ARQ_SESSION is False
|
||||||
|
assert static.TNC_STATE == "IDLE"
|
||||||
|
assert static.ARQ_SESSION_STATE == "disconnected"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
|
||||||
|
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
|
||||||
|
def test_foreign_disconnect(mycall: str, dxcall: str):
|
||||||
|
"""
|
||||||
|
Execute test to validate that receiving a session open frame sets the correct machine
|
||||||
|
state.
|
||||||
|
|
||||||
|
:param mycall: Callsign of the near station
|
||||||
|
:type mycall: str
|
||||||
|
:param dxcall: Callsign of the far station
|
||||||
|
:type dxcall: str
|
||||||
|
:return: Bytearray of the requested frame
|
||||||
|
:rtype: bytearray
|
||||||
|
"""
|
||||||
|
# Setup the static parameters for the connection.
|
||||||
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
||||||
|
static.MYCALLSIGN = mycallsign
|
||||||
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign)
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
static.DXCALLSIGN = dxcallsign
|
||||||
|
static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
|
# Create the TNC
|
||||||
|
tnc = data_handler.DATA()
|
||||||
|
|
||||||
|
# Replace the heartbeat transmit routine with a No-Op.
|
||||||
|
tnc.transmit_session_heartbeat = t_tsh_dummy
|
||||||
|
|
||||||
|
# Create frame to be 'received' by this station.
|
||||||
|
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
|
||||||
|
print_frame(create_frame)
|
||||||
|
tnc.received_session_opener(create_frame)
|
||||||
|
|
||||||
|
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes
|
||||||
|
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes
|
||||||
|
|
||||||
|
assert static.ARQ_SESSION is True
|
||||||
|
assert static.TNC_STATE == "BUSY"
|
||||||
|
assert static.ARQ_SESSION_STATE == "connecting"
|
||||||
|
|
||||||
|
# Set up a frame from a non-associated station.
|
||||||
|
foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
||||||
|
foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
|
||||||
|
|
||||||
|
close_frame = t_create_session_close("ZZ0ZZ-0", "ZZ0ZZ-0")
|
||||||
|
print_frame(close_frame)
|
||||||
|
assert (
|
||||||
|
helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False
|
||||||
|
)
|
||||||
|
|
||||||
|
assert helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
|
||||||
|
|
||||||
|
# Send the non-associated session close frame to the TNC
|
||||||
|
tnc.received_session_close(close_frame)
|
||||||
|
|
||||||
|
assert helpers.callsign_to_bytes(static.MYCALLSIGN) == helpers.callsign_to_bytes(
|
||||||
|
mycall
|
||||||
|
)
|
||||||
|
assert helpers.callsign_to_bytes(static.DXCALLSIGN) == helpers.callsign_to_bytes(
|
||||||
|
dxcall
|
||||||
|
)
|
||||||
|
|
||||||
|
assert static.ARQ_SESSION is True
|
||||||
|
assert static.TNC_STATE == "BUSY"
|
||||||
|
assert static.ARQ_SESSION_STATE == "connecting"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Run pytest with the current script as the filename.
|
||||||
|
ecode = pytest.main(["-v", sys.argv[0]])
|
||||||
|
if ecode == 0:
|
||||||
|
print("errors: 0")
|
||||||
|
else:
|
||||||
|
print(ecode)
|
245
test/test_tx.py
245
test/test_tx.py
|
@ -2,174 +2,217 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import *
|
|
||||||
import pathlib
|
|
||||||
import sounddevice as sd
|
|
||||||
import time
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0,'..')
|
|
||||||
from tnc import codec2
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import sounddevice as sd
|
||||||
|
|
||||||
# GET PARAMETER INPUTS
|
sys.path.insert(0, "..")
|
||||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
from tnc import codec2
|
||||||
parser.add_argument('--bursts', dest="N_BURSTS", default=1, type=int)
|
|
||||||
parser.add_argument('--framesperburst', dest="N_FRAMES_PER_BURST", default=1, type=int)
|
|
||||||
parser.add_argument('--delay', dest="DELAY_BETWEEN_BURSTS", default=500, type=int,
|
|
||||||
help="delay between bursts in ms")
|
|
||||||
parser.add_argument('--mode', dest="FREEDV_MODE", type=str, choices=['datac0', 'datac1', 'datac3'])
|
|
||||||
parser.add_argument('--audiodev', dest="AUDIO_OUTPUT_DEVICE", default=-1, type=int,
|
|
||||||
help="audio output device number to use, use -2 to automatically select a loopback device")
|
|
||||||
parser.add_argument('--list', dest="LIST", action="store_true", help="list audio devices by number and exit")
|
|
||||||
parser.add_argument('--testframes', dest="TESTFRAMES", action="store_true", default=False, help="list audio devices by number and exit")
|
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
def test_tx():
|
||||||
|
args = parse_arguments()
|
||||||
if args.LIST:
|
|
||||||
|
|
||||||
|
if args.LIST:
|
||||||
devices = sd.query_devices(device=None, kind=None)
|
devices = sd.query_devices(device=None, kind=None)
|
||||||
index = 0
|
|
||||||
for device in devices:
|
for index, device in enumerate(devices):
|
||||||
print(f"{index} {device['name']}")
|
print(f"{index} {device['name']}")
|
||||||
index += 1
|
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
quit()
|
sys.exit()
|
||||||
|
|
||||||
N_BURSTS = args.N_BURSTS
|
N_BURSTS = args.N_BURSTS
|
||||||
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST
|
||||||
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS/1000
|
DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000
|
||||||
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE
|
||||||
|
|
||||||
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value
|
||||||
|
|
||||||
# AUDIO PARAMETERS
|
# AUDIO PARAMETERS
|
||||||
AUDIO_FRAMES_PER_BUFFER = 2400
|
AUDIO_FRAMES_PER_BUFFER = 2400
|
||||||
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||||
AUDIO_SAMPLE_RATE_TX = 48000
|
AUDIO_SAMPLE_RATE_TX = 48000
|
||||||
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
|
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
|
||||||
|
|
||||||
# check if we want to use an audio device then do an pyaudio init
|
# check if we want to use an audio device then do an pyaudio init
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
# auto search for loopback devices
|
# auto search for loopback devices
|
||||||
if AUDIO_OUTPUT_DEVICE == -2:
|
if AUDIO_OUTPUT_DEVICE == -2:
|
||||||
loopback_list = []
|
loopback_list = []
|
||||||
|
|
||||||
devices = sd.query_devices(device=None, kind=None)
|
devices = sd.query_devices(device=None, kind=None)
|
||||||
index = 0
|
|
||||||
|
|
||||||
for device in devices:
|
for index, device in enumerate(devices):
|
||||||
if 'Loopback: PCM' in device['name']:
|
if "Loopback: PCM" in device["name"]:
|
||||||
print(index)
|
print(index)
|
||||||
loopback_list.append(index)
|
loopback_list.append(index)
|
||||||
index += 1
|
|
||||||
|
|
||||||
if len(loopback_list) >= 1:
|
if loopback_list:
|
||||||
AUDIO_OUTPUT_DEVICE = loopback_list[len(loopback_list)-1] #0 = RX 1 = TX
|
# 0 = RX 1 = TX
|
||||||
|
AUDIO_OUTPUT_DEVICE = loopback_list[-1]
|
||||||
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
print(f"loopback_list tx: {loopback_list}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
print("not enough audio loopback devices ready...")
|
print("not enough audio loopback devices ready...")
|
||||||
print("you should wait about 30 seconds...")
|
print("you should wait about 30 seconds...")
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
quit()
|
sys.exit()
|
||||||
print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE}", file=sys.stderr)
|
print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE}", file=sys.stderr)
|
||||||
|
|
||||||
# audio stream init
|
# audio stream init
|
||||||
stream_tx = sd.RawStream(channels=1, dtype='int16', device=(0, AUDIO_OUTPUT_DEVICE), samplerate = AUDIO_SAMPLE_RATE_TX, blocksize=4800)
|
stream_tx = sd.RawStream(
|
||||||
resampler = codec2.resampler()
|
channels=1,
|
||||||
|
dtype="int16",
|
||||||
|
device=(0, AUDIO_OUTPUT_DEVICE),
|
||||||
|
samplerate=AUDIO_SAMPLE_RATE_TX,
|
||||||
|
blocksize=4800,
|
||||||
|
)
|
||||||
|
|
||||||
# data binary string
|
resampler = codec2.resampler()
|
||||||
if args.TESTFRAMES:
|
|
||||||
|
# data binary string
|
||||||
|
if args.TESTFRAMES:
|
||||||
data_out = bytearray(14)
|
data_out = bytearray(14)
|
||||||
data_out[:1] = bytes([255])
|
data_out[:1] = bytes([255])
|
||||||
data_out[1:2] = bytes([1])
|
data_out[1:2] = bytes([1])
|
||||||
data_out[2:] = b'HELLO WORLD'
|
data_out[2:] = b"HELLO WORLD"
|
||||||
|
else:
|
||||||
|
data_out = b"HELLO WORLD!"
|
||||||
|
|
||||||
else:
|
# ----------------------------------------------------------------
|
||||||
data_out = b'HELLO WORLD!'
|
|
||||||
|
|
||||||
|
# Open codec2 instance
|
||||||
|
freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.c_void_p)
|
||||||
|
|
||||||
# ----------------------------------------------------------------
|
# Get number of bytes per frame for mode
|
||||||
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
|
payload_bytes_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
|
# Init buffer for data
|
||||||
|
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||||
|
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
||||||
|
|
||||||
|
# Init buffer for preample
|
||||||
|
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||||
|
freedv
|
||||||
|
)
|
||||||
|
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
||||||
|
|
||||||
# open codec2 instance
|
# Init buffer for postamble
|
||||||
freedv = cast(codec2.api.freedv_open(MODE), c_void_p)
|
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(
|
||||||
|
freedv
|
||||||
|
)
|
||||||
|
mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2)
|
||||||
|
|
||||||
# get number of bytes per frame for mode
|
# Create buffer for data
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv)/8)
|
# Use this if CRC16 checksum is required (DATA1-3)
|
||||||
payload_bytes_per_frame = bytes_per_frame -2
|
buffer = bytearray(payload_bytes_per_frame)
|
||||||
|
# set buffersize to length of data which will be send
|
||||||
|
buffer[: len(data_out)] = data_out
|
||||||
|
|
||||||
# init buffer for data
|
# Create CRC for data frame - we are using the CRC function shipped with codec2 to avoid
|
||||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
# CRC algorithm incompatibilities
|
||||||
mod_out = create_string_buffer(n_tx_modem_samples * 2)
|
# generate CRC16
|
||||||
|
crc = ctypes.c_ushort(
|
||||||
|
codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)
|
||||||
|
)
|
||||||
|
crc = crc.value.to_bytes(2, byteorder="big") # convert crc to 2 byte hex string
|
||||||
|
buffer += crc # append crc16 to buffer
|
||||||
|
|
||||||
# init buffer for preample
|
print(
|
||||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
|
f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}",
|
||||||
mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2)
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# init buffer for postamble
|
for brst in range(1, N_BURSTS + 1):
|
||||||
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
# Write preamble to txbuffer
|
||||||
mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2)
|
|
||||||
|
|
||||||
|
|
||||||
# create buffer for data
|
|
||||||
buffer = bytearray(payload_bytes_per_frame) # use this if CRC16 checksum is required ( DATA1-3)
|
|
||||||
buffer[:len(data_out)] = data_out # set buffersize to length of data which will be send
|
|
||||||
|
|
||||||
# create crc for data frame - we are using the crc function shipped with codec2 to avoid
|
|
||||||
# crc algorithm incompatibilities
|
|
||||||
crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # generate CRC16
|
|
||||||
crc = crc.value.to_bytes(2, byteorder='big') # convert crc to 2 byte hex string
|
|
||||||
buffer += crc # append crc16 to buffer
|
|
||||||
|
|
||||||
print(f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}", file=sys.stderr)
|
|
||||||
|
|
||||||
for i in range(1,N_BURSTS+1):
|
|
||||||
|
|
||||||
# write preamble to txbuffer
|
|
||||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
txbuffer = bytes(mod_out_preamble)
|
txbuffer = bytes(mod_out_preamble)
|
||||||
|
|
||||||
# create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
# Create modulaton for N = FRAMESPERBURST and append it to txbuffer
|
||||||
for n in range(1,N_FRAMES_PER_BURST+1):
|
for frm in range(1, N_FRAMES_PER_BURST + 1):
|
||||||
|
|
||||||
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer)
|
||||||
codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer
|
# Modulate DATA and save it into mod_out pointer
|
||||||
|
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||||
|
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
|
|
||||||
print(f"TX BURST: {i}/{N_BURSTS} FRAME: {n}/{N_FRAMES_PER_BURST}", file=sys.stderr)
|
print(
|
||||||
|
f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# append postamble to txbuffer
|
# Append postamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||||
txbuffer += bytes(mod_out_postamble)
|
txbuffer += bytes(mod_out_postamble)
|
||||||
|
|
||||||
# append a delay between bursts as audio silence
|
# Append a delay between bursts as audio silence
|
||||||
samples_delay = int(MODEM_SAMPLE_RATE*DELAY_BETWEEN_BURSTS)
|
samples_delay = int(MODEM_SAMPLE_RATE * DELAY_BETWEEN_BURSTS)
|
||||||
mod_out_silence = create_string_buffer(samples_delay*2)
|
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
|
||||||
txbuffer += bytes(mod_out_silence)
|
txbuffer += bytes(mod_out_silence)
|
||||||
#print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr)
|
# print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr)
|
||||||
|
|
||||||
# resample up to 48k (resampler works on np.int16)
|
# Resample up to 48k (resampler works on np.int16)
|
||||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
np_buffer = np.frombuffer(txbuffer, dtype=np.int16)
|
||||||
txbuffer_48k = resampler.resample8_to_48(x)
|
txbuffer_48k = resampler.resample8_to_48(np_buffer)
|
||||||
|
|
||||||
# check if we want to use an audio device or stdout
|
# Check if we want to use an audio device or stdout
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
stream_tx.start()
|
stream_tx.start()
|
||||||
stream_tx.write(txbuffer_48k)
|
stream_tx.write(txbuffer_48k)
|
||||||
else:
|
else:
|
||||||
# print data to terminal for piping the output to other programs
|
# Print data to terminal for piping the output to other programs
|
||||||
sys.stdout.buffer.write(txbuffer_48k)
|
sys.stdout.buffer.write(txbuffer_48k)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# and at last check if we had an opened audio instance and close it
|
||||||
# and at last check if we had an opened audio instance and close it
|
if AUDIO_OUTPUT_DEVICE != -1:
|
||||||
if AUDIO_OUTPUT_DEVICE != -1:
|
|
||||||
sd._terminate()
|
sd._terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
# GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description="Simons TEST TNC")
|
||||||
|
parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
"--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--delay",
|
||||||
|
dest="DELAY_BETWEEN_BURSTS",
|
||||||
|
default=500,
|
||||||
|
type=int,
|
||||||
|
help="delay between bursts in ms",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"]
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--audiodev",
|
||||||
|
dest="AUDIO_OUTPUT_DEVICE",
|
||||||
|
default=-1,
|
||||||
|
type=int,
|
||||||
|
help="audio output device number to use, use -2 to automatically select a loopback device",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--list",
|
||||||
|
dest="LIST",
|
||||||
|
action="store_true",
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--testframes",
|
||||||
|
dest="TESTFRAMES",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="list audio devices by number and exit",
|
||||||
|
)
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_tx()
|
||||||
|
|
|
@ -191,7 +191,7 @@ class DAEMON:
|
||||||
options.append('--radiocontrol')
|
options.append('--radiocontrol')
|
||||||
options.append(data[13])
|
options.append(data[13])
|
||||||
|
|
||||||
if data[13] != 'rigctld':
|
if data[13] == 'rigctld':
|
||||||
options.append('--rigctld_ip')
|
options.append('--rigctld_ip')
|
||||||
options.append(data[14])
|
options.append(data[14])
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,11 @@ DATA_QUEUE_TRANSMIT = queue.Queue()
|
||||||
DATA_QUEUE_RECEIVED = queue.Queue()
|
DATA_QUEUE_RECEIVED = queue.Queue()
|
||||||
|
|
||||||
|
|
||||||
class DATA():
|
class DATA:
|
||||||
""" Terminal Node Controller for FreeDATA """
|
""" Terminal Node Controller for FreeDATA """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mycallsign = static.MYCALLSIGN # initial callsign. Will be overwritten later
|
self.mycallsign = static.MYCALLSIGN # initial call sign. Will be overwritten later
|
||||||
|
|
||||||
self.data_queue_transmit = DATA_QUEUE_TRANSMIT
|
self.data_queue_transmit = DATA_QUEUE_TRANSMIT
|
||||||
self.data_queue_received = DATA_QUEUE_RECEIVED
|
self.data_queue_received = DATA_QUEUE_RECEIVED
|
||||||
|
@ -52,6 +53,7 @@ class DATA():
|
||||||
|
|
||||||
self.received_mycall_crc = b'' # Received my callsign crc if we received a crc for another ssid
|
self.received_mycall_crc = b'' # Received my callsign crc if we received a crc for another ssid
|
||||||
|
|
||||||
|
|
||||||
self.data_channel_last_received = 0.0 # time of last "live sign" of a frame
|
self.data_channel_last_received = 0.0 # time of last "live sign" of a frame
|
||||||
self.burst_ack_snr = 0 # SNR from received ack frames
|
self.burst_ack_snr = 0 # SNR from received ack frames
|
||||||
self.burst_ack = False # if we received an acknowledge frame for a burst
|
self.burst_ack = False # if we received an acknowledge frame for a burst
|
||||||
|
@ -59,6 +61,7 @@ class DATA():
|
||||||
self.rpt_request_received = False # if we received an request for repeater frames
|
self.rpt_request_received = False # if we received an request for repeater frames
|
||||||
self.rpt_request_buffer = [] # requested frames, saved in a list
|
self.rpt_request_buffer = [] # requested frames, saved in a list
|
||||||
self.rx_start_of_transmission = 0 # time of transmission start
|
self.rx_start_of_transmission = 0 # time of transmission start
|
||||||
|
|
||||||
self.data_frame_bof = b'BOF' # 2 bytes for the BOF End of File indicator in a data frame
|
self.data_frame_bof = b'BOF' # 2 bytes for the BOF End of File indicator in a data frame
|
||||||
self.data_frame_eof = b'EOF' # 2 bytes for the EOF End of File indicator in a data frame
|
self.data_frame_eof = b'EOF' # 2 bytes for the EOF End of File indicator in a data frame
|
||||||
|
|
||||||
|
@ -73,17 +76,19 @@ class DATA():
|
||||||
self.mode_list_low_bw = [14, 12]
|
self.mode_list_low_bw = [14, 12]
|
||||||
self.time_list_low_bw = [3, 7]
|
self.time_list_low_bw = [3, 7]
|
||||||
|
|
||||||
self.mode_list_high_bw = [14, 12, 10] # 201 = FSK mode list of available modes, each mode will be used 2times per speed level
|
|
||||||
self.time_list_high_bw = [3, 7, 8, 30] # list for time to wait for correspinding mode in seconds
|
|
||||||
|
|
||||||
# mode list for selecting between low bandwith ( 500Hz ) and normal modes with higher bandwith
|
self.mode_list_high_bw = [14, 12, 10] # mode list of available modes,each mode will be used 2 times per level
|
||||||
|
self.time_list_high_bw = [3, 7, 8, 30] # list for time to wait for corresponding mode in seconds
|
||||||
|
|
||||||
|
# mode list for selecting between low bandwidth ( 500Hz ) and normal modes with higher bandwidth
|
||||||
if static.LOW_BANDWITH_MODE:
|
if static.LOW_BANDWITH_MODE:
|
||||||
self.mode_list = self.mode_list_low_bw # mode list of available modes, each mode will be used 2times per speed level
|
self.mode_list = self.mode_list_low_bw # mode list of available modes, each mode will be used 2times per speed level
|
||||||
self.time_list = self.time_list_low_bw # list for time to wait for correspinding mode in seconds
|
|
||||||
|
self.time_list = self.time_list_low_bw # list for time to wait for corresponding mode in seconds
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.mode_list = self.mode_list_high_bw # mode list of available modes, each mode will be used 2times per speed level
|
self.mode_list = self.mode_list_high_bw # mode list of available modes, each mode will be used 2times per speed level
|
||||||
self.time_list = self.time_list_high_bw # list for time to wait for correspinding mode in seconds
|
self.time_list = self.time_list_high_bw # list for time to wait for corresponding mode in seconds
|
||||||
|
|
||||||
self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode
|
self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode
|
||||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||||
|
@ -98,7 +103,9 @@ class DATA():
|
||||||
|
|
||||||
self.transmission_timeout = 360 # transmission timeout in seconds
|
self.transmission_timeout = 360 # transmission timeout in seconds
|
||||||
|
|
||||||
worker_thread_transmit = threading.Thread(target=self.worker_transmit, name="worker thread transmit", daemon=True)
|
|
||||||
|
worker_thread_transmit = threading.Thread(target=self.worker_transmit, name="worker thread transmit",
|
||||||
|
daemon=True)
|
||||||
worker_thread_transmit.start()
|
worker_thread_transmit.start()
|
||||||
|
|
||||||
worker_thread_receive = threading.Thread(target=self.worker_receive, name="worker thread receive", daemon=True)
|
worker_thread_receive = threading.Thread(target=self.worker_receive, name="worker thread receive", daemon=True)
|
||||||
|
@ -284,7 +291,7 @@ class DATA():
|
||||||
self.arq_received_channel_is_open(bytes_out[:-2])
|
self.arq_received_channel_is_open(bytes_out[:-2])
|
||||||
|
|
||||||
# ARQ MANUAL MODE TRANSMISSION
|
# ARQ MANUAL MODE TRANSMISSION
|
||||||
elif 230 <= frametype <= 240 :
|
elif 230 <= frametype <= 240:
|
||||||
structlog.get_logger("structlog").debug("[TNC] ARQ manual mode")
|
structlog.get_logger("structlog").debug("[TNC] ARQ manual mode")
|
||||||
self.arq_received_data_channel_opener(bytes_out[:-2])
|
self.arq_received_data_channel_opener(bytes_out[:-2])
|
||||||
|
|
||||||
|
@ -365,7 +372,7 @@ class DATA():
|
||||||
# set n frames per burst to modem
|
# set n frames per burst to modem
|
||||||
# this is an idea, so it's not getting lost....
|
# this is an idea, so it's not getting lost....
|
||||||
# we need to work on this
|
# we need to work on this
|
||||||
codec2.api.freedv_set_frames_per_burst(freedv,len(missing_frames))
|
codec2.api.freedv_set_frames_per_burst(freedv, len(missing_frames))
|
||||||
|
|
||||||
# TODO: Trim `missing_frames` bytesarray to [7:13] (6) frames, if it's larger.
|
# TODO: Trim `missing_frames` bytesarray to [7:13] (6) frames, if it's larger.
|
||||||
|
|
||||||
|
@ -443,8 +450,6 @@ class DATA():
|
||||||
|
|
||||||
self.arq_file_transfer = True
|
self.arq_file_transfer = True
|
||||||
|
|
||||||
RX_PAYLOAD_PER_MODEM_FRAME = bytes_per_frame - 2 # payload per moden frame
|
|
||||||
|
|
||||||
static.TNC_STATE = 'BUSY'
|
static.TNC_STATE = 'BUSY'
|
||||||
static.ARQ_STATE = True
|
static.ARQ_STATE = True
|
||||||
static.INFO.append("ARQ;RECEIVING")
|
static.INFO.append("ARQ;RECEIVING")
|
||||||
|
@ -465,7 +470,8 @@ class DATA():
|
||||||
|
|
||||||
structlog.get_logger("structlog").debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER)
|
structlog.get_logger("structlog").debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER)
|
||||||
|
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', snr, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', snr, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
# Check if we received all frames in the burst by checking if burst buffer has no more "Nones"
|
# Check if we received all frames in the burst by checking if burst buffer has no more "Nones"
|
||||||
# This is the ideal case because we received all data
|
# This is the ideal case because we received all data
|
||||||
|
@ -492,7 +498,7 @@ class DATA():
|
||||||
# search_area --> area where we want to search
|
# search_area --> area where we want to search
|
||||||
search_area = 510
|
search_area = 510
|
||||||
|
|
||||||
search_position = len(static.RX_FRAME_BUFFER)-search_area
|
search_position = len(static.RX_FRAME_BUFFER) - search_area
|
||||||
# find position of data. returns -1 if nothing found in area else >= 0
|
# find position of data. returns -1 if nothing found in area else >= 0
|
||||||
# we are beginning from the end, so if data exists twice or more, only the last one should be replaced
|
# we are beginning from the end, so if data exists twice or more, only the last one should be replaced
|
||||||
get_position = static.RX_FRAME_BUFFER[search_position:].rfind(temp_burst_buffer)
|
get_position = static.RX_FRAME_BUFFER[search_position:].rfind(temp_burst_buffer)
|
||||||
|
@ -500,8 +506,10 @@ class DATA():
|
||||||
if get_position >= 0:
|
if get_position >= 0:
|
||||||
static.RX_FRAME_BUFFER = static.RX_FRAME_BUFFER[:search_position + get_position]
|
static.RX_FRAME_BUFFER = static.RX_FRAME_BUFFER[:search_position + get_position]
|
||||||
static.RX_FRAME_BUFFER += temp_burst_buffer
|
static.RX_FRAME_BUFFER += temp_burst_buffer
|
||||||
structlog.get_logger("structlog").warning("[TNC] ARQ | RX | replacing existing buffer data", area=search_area, pos=get_position)
|
|
||||||
# if we don't find data n this range, we really have new data and going to replace it
|
structlog.get_logger("structlog").warning("[TNC] ARQ | RX | replacing existing buffer data",
|
||||||
|
area=search_area, pos=get_position)
|
||||||
|
# if we dont find data n this range, we really have new data and going to replace it
|
||||||
else:
|
else:
|
||||||
static.RX_FRAME_BUFFER += temp_burst_buffer
|
static.RX_FRAME_BUFFER += temp_burst_buffer
|
||||||
structlog.get_logger("structlog").debug("[TNC] ARQ | RX | appending data to buffer")
|
structlog.get_logger("structlog").debug("[TNC] ARQ | RX | appending data to buffer")
|
||||||
|
@ -532,29 +540,31 @@ class DATA():
|
||||||
# calculate statistics
|
# calculate statistics
|
||||||
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
||||||
|
|
||||||
elif RX_N_FRAME_OF_BURST == RX_N_FRAMES_PER_BURST -1:
|
elif RX_N_FRAME_OF_BURST == RX_N_FRAMES_PER_BURST - 1:
|
||||||
# We have "Nones" in our rx buffer,
|
# We have "Nones" in our rx buffer,
|
||||||
# Check if we received last frame of burst - this is an indicator for missed frames.
|
# Check if we received last frame of burst - this is an indicator for missed frames.
|
||||||
# With this way of doing this, we always MUST receive the last frame of a burst otherwise the entire
|
# With this way of doing this, we always MUST receive the last frame of a burst otherwise the entire
|
||||||
# burst is lost
|
# burst is lost
|
||||||
structlog.get_logger("structlog").debug("[TNC] all frames in burst received:", frame=RX_N_FRAME_OF_BURST, frames=RX_N_FRAMES_PER_BURST)
|
structlog.get_logger("structlog").debug("[TNC] all frames in burst received:", frame=RX_N_FRAME_OF_BURST,
|
||||||
|
frames=RX_N_FRAMES_PER_BURST)
|
||||||
self.send_retransmit_request_frame(freedv)
|
self.send_retransmit_request_frame(freedv)
|
||||||
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
||||||
|
|
||||||
# Should never reach this point
|
# Should never reach this point
|
||||||
else:
|
else:
|
||||||
structlog.get_logger("structlog").error("[TNC] data_handler: Should not reach this point...", frame=RX_N_FRAME_OF_BURST, frames=RX_N_FRAMES_PER_BURST)
|
structlog.get_logger("structlog").error("[TNC] data_handler: Should not reach this point...",
|
||||||
|
frame=RX_N_FRAME_OF_BURST, frames=RX_N_FRAMES_PER_BURST)
|
||||||
|
|
||||||
# We have a BOF and EOF flag in our data. If we received both we received our frame.
|
# We have a BOF and EOF flag in our data. If we received both we received our frame.
|
||||||
# In case of loosing data but we received already a BOF and EOF we need to make sure, we
|
# In case of loosing data, but we received already a BOF and EOF we need to make sure, we
|
||||||
# received the complete last burst by checking it for Nones
|
# received the complete last burst by checking it for Nones
|
||||||
bof_position = static.RX_FRAME_BUFFER.find(self.data_frame_bof)
|
bof_position = static.RX_FRAME_BUFFER.find(self.data_frame_bof)
|
||||||
eof_position = static.RX_FRAME_BUFFER.find(self.data_frame_eof)
|
eof_position = static.RX_FRAME_BUFFER.find(self.data_frame_eof)
|
||||||
|
|
||||||
# get total bytes per transmission information as soon we recevied a frame with a BOF
|
# get total bytes per transmission information as soon we recevied a frame with a BOF
|
||||||
if bof_position >=0:
|
|
||||||
|
|
||||||
payload = static.RX_FRAME_BUFFER[bof_position+len(self.data_frame_bof):eof_position]
|
if bof_position >= 0:
|
||||||
|
payload = static.RX_FRAME_BUFFER[bof_position + len(self.data_frame_bof):eof_position]
|
||||||
frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes
|
frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes
|
||||||
static.TOTAL_BYTES = frame_length
|
static.TOTAL_BYTES = frame_length
|
||||||
compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes
|
compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes
|
||||||
|
@ -563,13 +573,14 @@ class DATA():
|
||||||
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
||||||
|
|
||||||
if bof_position >= 0 and eof_position > 0 and None not in static.RX_BURST_BUFFER:
|
if bof_position >= 0 and eof_position > 0 and None not in static.RX_BURST_BUFFER:
|
||||||
structlog.get_logger("structlog").debug("[TNC] arq_data_received:", bof_position=bof_position, eof_position=eof_position)
|
structlog.get_logger("structlog").debug("[TNC] arq_data_received:", bof_position=bof_position,
|
||||||
|
eof_position=eof_position)
|
||||||
# print(f"bof_position {bof_position} / eof_position {eof_position}")
|
# print(f"bof_position {bof_position} / eof_position {eof_position}")
|
||||||
self.rx_frame_bof_received = True
|
self.rx_frame_bof_received = True
|
||||||
self.rx_frame_eof_received = True
|
self.rx_frame_eof_received = True
|
||||||
|
|
||||||
# Extract raw data from buffer
|
# Extract raw data from buffer
|
||||||
payload = static.RX_FRAME_BUFFER[bof_position+len(self.data_frame_bof):eof_position]
|
payload = static.RX_FRAME_BUFFER[bof_position + len(self.data_frame_bof):eof_position]
|
||||||
# Get the data frame crc
|
# Get the data frame crc
|
||||||
data_frame_crc = payload[:4] # 0:4 4bytes
|
data_frame_crc = payload[:4] # 0:4 4bytes
|
||||||
# Get the data frame length
|
# Get the data frame length
|
||||||
|
@ -603,23 +614,30 @@ class DATA():
|
||||||
# Re-code data_frame in base64, UTF-8 for JSON UI communication.
|
# Re-code data_frame in base64, UTF-8 for JSON UI communication.
|
||||||
base64_data = base64.b64encode(data_frame).decode("utf-8")
|
base64_data = base64.b64encode(data_frame).decode("utf-8")
|
||||||
static.RX_BUFFER.append([uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data])
|
static.RX_BUFFER.append([uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data])
|
||||||
jsondata = {"arq":"received", "uuid" : uniqueid, "timestamp": timestamp, "mycallsign" : str(mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'), "dxgrid": str(static.DXGRID, 'utf-8'), "data": base64_data}
|
jsondata = {"arq": "received", "uuid": uniqueid, "timestamp": timestamp,
|
||||||
|
"mycallsign": str(mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'),
|
||||||
|
"dxgrid": str(static.DXGRID, 'utf-8'), "data": base64_data}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
structlog.get_logger("structlog").debug("[TNC] arq_data_received:", jsondata=jsondata)
|
structlog.get_logger("structlog").debug("[TNC] arq_data_received:", jsondata=jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
static.INFO.append("ARQ;RECEIVING;SUCCESS")
|
static.INFO.append("ARQ;RECEIVING;SUCCESS")
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | RX | SENDING DATA FRAME ACK", snr=snr, crc=data_frame_crc.hex())
|
structlog.get_logger("structlog").info("[TNC] ARQ | RX | SENDING DATA FRAME ACK", snr=snr,
|
||||||
|
crc=data_frame_crc.hex())
|
||||||
self.send_data_ack_frame(snr)
|
self.send_data_ack_frame(snr)
|
||||||
# update our statistics AFTER the frame ACK
|
# update our statistics AFTER the frame ACK
|
||||||
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER))
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] | RX | DATACHANNEL [" +
|
structlog.get_logger("structlog").info("[TNC] | RX | DATACHANNEL [" +
|
||||||
str(self.mycallsign, 'utf-8') + "]<< >>[" + str(static.DXCALLSIGN, 'utf-8') + "]", snr=snr)
|
str(self.mycallsign, 'utf-8') + "]<< >>[" + str(
|
||||||
|
static.DXCALLSIGN, 'utf-8') + "]", snr=snr)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
static.INFO.append("ARQ;RECEIVING;FAILED")
|
static.INFO.append("ARQ;RECEIVING;FAILED")
|
||||||
structlog.get_logger("structlog").warning("[TNC] ARQ | RX | DATA FRAME NOT SUCESSFULLY RECEIVED!", e="wrong crc", expected=data_frame_crc, received=data_frame_crc_received, overflows=static.BUFFER_OVERFLOW_COUNTER)
|
structlog.get_logger("structlog").warning("[TNC] ARQ | RX | DATA FRAME NOT SUCESSFULLY RECEIVED!",
|
||||||
|
e="wrong crc", expected=data_frame_crc,
|
||||||
|
received=data_frame_crc_received,
|
||||||
|
overflows=static.BUFFER_OVERFLOW_COUNTER)
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | RX | Sending NACK")
|
structlog.get_logger("structlog").info("[TNC] ARQ | RX | Sending NACK")
|
||||||
self.send_burst_nack_frame(snr)
|
self.send_burst_nack_frame(snr)
|
||||||
|
@ -632,7 +650,7 @@ class DATA():
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
|
||||||
def arq_transmit(self, data_out:bytes, mode:int, n_frames_per_burst:int):
|
def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -665,7 +683,8 @@ class DATA():
|
||||||
frame_total_size = len(data_out).to_bytes(4, byteorder='big')
|
frame_total_size = len(data_out).to_bytes(4, byteorder='big')
|
||||||
static.INFO.append("ARQ;TRANSMITTING")
|
static.INFO.append("ARQ;TRANSMITTING")
|
||||||
|
|
||||||
jsondata = {"arq":"transmission", "status" :"transmitting", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
jsondata = {"arq": "transmission", "status": "transmitting", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
|
@ -690,7 +709,7 @@ class DATA():
|
||||||
# data_out = self.data_frame_bof + frame_payload_crc + data_out + self.data_frame_eof
|
# data_out = self.data_frame_bof + frame_payload_crc + data_out + self.data_frame_eof
|
||||||
data_out = self.data_frame_bof + frame_payload_crc + frame_total_size + compression_factor + data_out + self.data_frame_eof
|
data_out = self.data_frame_bof + frame_payload_crc + frame_total_size + compression_factor + data_out + self.data_frame_eof
|
||||||
|
|
||||||
#initial bufferposition is 0
|
# initial bufferposition is 0
|
||||||
bufferposition = bufferposition_end = 0
|
bufferposition = bufferposition_end = 0
|
||||||
|
|
||||||
# iterate through data out buffer
|
# iterate through data out buffer
|
||||||
|
@ -730,10 +749,11 @@ class DATA():
|
||||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||||
data_mode = self.mode_list[self.speed_level]
|
data_mode = self.mode_list[self.speed_level]
|
||||||
|
|
||||||
structlog.get_logger("structlog").debug("[TNC] Speed-level:", level=self.speed_level, retry=self.tx_n_retry_of_burst, mode=data_mode)
|
structlog.get_logger("structlog").debug("[TNC] Speed-level:", level=self.speed_level,
|
||||||
|
retry=self.tx_n_retry_of_burst, mode=data_mode)
|
||||||
|
|
||||||
# payload information
|
# payload information
|
||||||
payload_per_frame = modem.get_bytes_per_frame(data_mode) -2
|
payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2
|
||||||
|
|
||||||
# tempbuffer list for storing our data frames
|
# tempbuffer list for storing our data frames
|
||||||
tempbuffer = []
|
tempbuffer = []
|
||||||
|
@ -762,19 +782,20 @@ class DATA():
|
||||||
# the last bytes of a frame
|
# the last bytes of a frame
|
||||||
else:
|
else:
|
||||||
extended_data_out = data_out[bufferposition:]
|
extended_data_out = data_out[bufferposition:]
|
||||||
extended_data_out += bytes([0]) * (payload_per_frame-len(extended_data_out)-len(arqheader))
|
extended_data_out += bytes([0]) * (payload_per_frame - len(extended_data_out) - len(arqheader))
|
||||||
frame = arqheader + extended_data_out
|
frame = arqheader + extended_data_out
|
||||||
|
|
||||||
# append frame to tempbuffer for transmission
|
# append frame to tempbuffer for transmission
|
||||||
tempbuffer.append(frame)
|
tempbuffer.append(frame)
|
||||||
|
|
||||||
structlog.get_logger("structlog").debug("[TNC] tempbuffer:", tempbuffer=tempbuffer)
|
structlog.get_logger("structlog").debug("[TNC] tempbuffer:", tempbuffer=tempbuffer)
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | TX | FRAMES", mode=data_mode, fpb=TX_N_FRAMES_PER_BURST, retry=self.tx_n_retry_of_burst)
|
structlog.get_logger("structlog").info("[TNC] ARQ | TX | FRAMES", mode=data_mode,
|
||||||
|
fpb=TX_N_FRAMES_PER_BURST, retry=self.tx_n_retry_of_burst)
|
||||||
|
|
||||||
# we need to set our TRANSMITTING flag before we are adding an object the transmit queue
|
# we need to set our TRANSMITTING flag before we are adding an object the transmit queue
|
||||||
# this is not that nice, we could improve this somehow
|
# this is not that nice, we could improve this somehow
|
||||||
static.TRANSMITTING = True
|
static.TRANSMITTING = True
|
||||||
modem.MODEM_TRANSMIT_QUEUE.put([data_mode,1,0,tempbuffer])
|
modem.MODEM_TRANSMIT_QUEUE.put([data_mode, 1, 0, tempbuffer])
|
||||||
|
|
||||||
# wait while transmitting
|
# wait while transmitting
|
||||||
while static.TRANSMITTING:
|
while static.TRANSMITTING:
|
||||||
|
@ -794,6 +815,7 @@ class DATA():
|
||||||
|
|
||||||
# once we received a burst ack, reset its state and break the RETRIES loop
|
# once we received a burst ack, reset its state and break the RETRIES loop
|
||||||
if self.burst_ack:
|
if self.burst_ack:
|
||||||
|
|
||||||
self.burst_ack = False # reset ack state
|
self.burst_ack = False # reset ack state
|
||||||
self.tx_n_retry_of_burst = 0 # reset retries
|
self.tx_n_retry_of_burst = 0 # reset retries
|
||||||
break # break retry loop
|
break # break retry loop
|
||||||
|
@ -816,7 +838,9 @@ class DATA():
|
||||||
|
|
||||||
self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out))
|
self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out))
|
||||||
# NEXT ATTEMPT
|
# NEXT ATTEMPT
|
||||||
structlog.get_logger("structlog").debug("[TNC] ATTEMPT:", retry=self.tx_n_retry_of_burst, maxretries=TX_N_MAX_RETRIES_PER_BURST, overflows=static.BUFFER_OVERFLOW_COUNTER)
|
structlog.get_logger("structlog").debug("[TNC] ATTEMPT:", retry=self.tx_n_retry_of_burst,
|
||||||
|
maxretries=TX_N_MAX_RETRIES_PER_BURST,
|
||||||
|
overflows=static.BUFFER_OVERFLOW_COUNTER)
|
||||||
|
|
||||||
# update buffer position
|
# update buffer position
|
||||||
bufferposition = bufferposition_end
|
bufferposition = bufferposition_end
|
||||||
|
@ -824,7 +848,8 @@ class DATA():
|
||||||
# update stats
|
# update stats
|
||||||
self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out))
|
self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out))
|
||||||
|
|
||||||
jsondata = {"arq":"transmission", "status" :"transmitting", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
jsondata = {"arq": "transmission", "status": "transmitting", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
|
@ -832,19 +857,25 @@ class DATA():
|
||||||
|
|
||||||
if self.data_frame_ack_received:
|
if self.data_frame_ack_received:
|
||||||
static.INFO.append("ARQ;TRANSMITTING;SUCCESS")
|
static.INFO.append("ARQ;TRANSMITTING;SUCCESS")
|
||||||
jsondata = {"arq":"transmission", "status" :"success", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
jsondata = {"arq": "transmission", "status": "success", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | TX | DATA TRANSMITTED!", BytesPerMinute=static.ARQ_BYTES_PER_MINUTE, BitsPerSecond=static.ARQ_BITS_PER_SECOND, overflows=static.BUFFER_OVERFLOW_COUNTER)
|
structlog.get_logger("structlog").info("[TNC] ARQ | TX | DATA TRANSMITTED!",
|
||||||
|
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
|
||||||
|
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
|
||||||
|
overflows=static.BUFFER_OVERFLOW_COUNTER)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
static.INFO.append("ARQ;TRANSMITTING;FAILED")
|
static.INFO.append("ARQ;TRANSMITTING;FAILED")
|
||||||
jsondata = {"arq":"transmission", "status" :"failed", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", overflows=static.BUFFER_OVERFLOW_COUNTER)
|
structlog.get_logger("structlog").info("[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!",
|
||||||
|
overflows=static.BUFFER_OVERFLOW_COUNTER)
|
||||||
self.stop_transmission()
|
self.stop_transmission()
|
||||||
|
|
||||||
# and last but not least doing a state cleanup
|
# and last but not least doing a state cleanup
|
||||||
|
@ -856,7 +887,7 @@ class DATA():
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# signalling frames received
|
# signalling frames received
|
||||||
def burst_ack_received(self, data_in:bytes):
|
def burst_ack_received(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -872,11 +903,12 @@ class DATA():
|
||||||
|
|
||||||
# only process data if we are in ARQ and BUSY state
|
# only process data if we are in ARQ and BUSY state
|
||||||
if static.ARQ_STATE:
|
if static.ARQ_STATE:
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
self.burst_ack = True # Force data loops of TNC to stop and continue with next frame
|
self.burst_ack = True # Force data loops of TNC to stop and continue with next frame
|
||||||
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
self.burst_ack_snr= int.from_bytes(bytes(data_in[5:6]), "big")
|
self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big")
|
||||||
self.speed_level= int.from_bytes(bytes(data_in[6:7]), "big")
|
self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big")
|
||||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||||
structlog.get_logger("structlog").debug("[TNC] burst_ack_received:", speed_level=self.speed_level)
|
structlog.get_logger("structlog").debug("[TNC] burst_ack_received:", speed_level=self.speed_level)
|
||||||
# print(self.speed_level)
|
# print(self.speed_level)
|
||||||
|
@ -886,7 +918,7 @@ class DATA():
|
||||||
self.n_retries_per_burst = 0
|
self.n_retries_per_burst = 0
|
||||||
|
|
||||||
# signalling frames received
|
# signalling frames received
|
||||||
def burst_nack_received(self, data_in:bytes):
|
def burst_nack_received(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -902,11 +934,12 @@ class DATA():
|
||||||
|
|
||||||
# only process data if we are in ARQ and BUSY state
|
# only process data if we are in ARQ and BUSY state
|
||||||
if static.ARQ_STATE:
|
if static.ARQ_STATE:
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
self.burst_nack = True # Force data loops of TNC to stop and continue with next frame
|
self.burst_nack = True # Force data loops of TNC to stop and continue with next frame
|
||||||
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
self.burst_ack_snr= int.from_bytes(bytes(data_in[5:6]), "big")
|
self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big")
|
||||||
self.speed_level= int.from_bytes(bytes(data_in[6:7]), "big")
|
self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big")
|
||||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||||
self.burst_nack_counter += 1
|
self.burst_nack_counter += 1
|
||||||
structlog.get_logger("structlog").debug("[TNC] burst_nack_received:", speed_level=self.speed_level)
|
structlog.get_logger("structlog").debug("[TNC] burst_nack_received:", speed_level=self.speed_level)
|
||||||
|
@ -916,12 +949,13 @@ class DATA():
|
||||||
""" """
|
""" """
|
||||||
# only process data if we are in ARQ and BUSY state
|
# only process data if we are in ARQ and BUSY state
|
||||||
if static.ARQ_STATE:
|
if static.ARQ_STATE:
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
self.data_frame_ack_received = True # Force data loops of TNC to stop and continue with next frame
|
self.data_frame_ack_received = True # Force data loops of TNC to stop and continue with next frame
|
||||||
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
|
|
||||||
def frame_nack_received(self, data_in:bytes): # pylint: disable=unused-argument
|
def frame_nack_received(self, data_in: bytes): # pylint: disable=unused-argument
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -930,9 +964,11 @@ class DATA():
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
static.INFO.append("ARQ;TRANSMITTING;FAILED")
|
static.INFO.append("ARQ;TRANSMITTING;FAILED")
|
||||||
jsondata = {"arq":"transmission", "status" : "failed", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
|
@ -940,7 +976,7 @@ class DATA():
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
|
||||||
def burst_rpt_received(self, data_in:bytes):
|
def burst_rpt_received(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -951,7 +987,8 @@ class DATA():
|
||||||
"""
|
"""
|
||||||
# only process data if we are in ARQ and BUSY state
|
# only process data if we are in ARQ and BUSY state
|
||||||
if static.ARQ_STATE and static.TNC_STATE == 'BUSY':
|
if static.ARQ_STATE and static.TNC_STATE == 'BUSY':
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
self.rpt_request_received = True
|
self.rpt_request_received = True
|
||||||
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
|
@ -978,7 +1015,9 @@ class DATA():
|
||||||
"""
|
"""
|
||||||
# TODO: we need to check this, maybe placing it to class init
|
# TODO: we need to check this, maybe placing it to class init
|
||||||
self.datachannel_timeout = False
|
self.datachannel_timeout = False
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]", state=static.ARQ_SESSION_STATE)
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
state=static.ARQ_SESSION_STATE)
|
||||||
|
|
||||||
self.open_session(callsign)
|
self.open_session(callsign)
|
||||||
|
|
||||||
|
@ -1014,8 +1053,11 @@ class DATA():
|
||||||
|
|
||||||
while not static.ARQ_SESSION:
|
while not static.ARQ_SESSION:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
for attempt in range(1,self.session_connect_max_retries+1):
|
for attempt in range(1, self.session_connect_max_retries + 1):
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>?<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", a=attempt, state=static.ARQ_SESSION_STATE)
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>?<<[" + str(static.DXCALLSIGN,
|
||||||
|
'utf-8') + "]", a=attempt,
|
||||||
|
state=static.ARQ_SESSION_STATE)
|
||||||
|
|
||||||
self.enqueue_frame_for_tx(connection_frame)
|
self.enqueue_frame_for_tx(connection_frame)
|
||||||
|
|
||||||
|
@ -1035,7 +1077,7 @@ class DATA():
|
||||||
self.close_session()
|
self.close_session()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def received_session_opener(self, data_in:bytes):
|
def received_session_opener(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -1052,8 +1094,11 @@ class DATA():
|
||||||
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
||||||
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13]))
|
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13]))
|
||||||
|
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET,
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", state=static.ARQ_SESSION_STATE)
|
static.HAMLIB_FREQUENCY)
|
||||||
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
state=static.ARQ_SESSION_STATE)
|
||||||
static.ARQ_SESSION = True
|
static.ARQ_SESSION = True
|
||||||
static.TNC_STATE = 'BUSY'
|
static.TNC_STATE = 'BUSY'
|
||||||
|
|
||||||
|
@ -1062,8 +1107,11 @@ class DATA():
|
||||||
def close_session(self):
|
def close_session(self):
|
||||||
""" Close the ARQ session """
|
""" Close the ARQ session """
|
||||||
static.ARQ_SESSION_STATE = 'disconnecting'
|
static.ARQ_SESSION_STATE = 'disconnecting'
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET,
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<X>>[" + str(static.DXCALLSIGN, 'utf-8') + "]", state=static.ARQ_SESSION_STATE)
|
static.HAMLIB_FREQUENCY)
|
||||||
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<X>>[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
state=static.ARQ_SESSION_STATE)
|
||||||
static.INFO.append("ARQ;SESSION;CLOSE")
|
static.INFO.append("ARQ;SESSION;CLOSE")
|
||||||
self.IS_ARQ_SESSION_MASTER = False
|
self.IS_ARQ_SESSION_MASTER = False
|
||||||
static.ARQ_SESSION = False
|
static.ARQ_SESSION = False
|
||||||
|
@ -1072,7 +1120,7 @@ class DATA():
|
||||||
|
|
||||||
self.send_disconnect_frame()
|
self.send_disconnect_frame()
|
||||||
|
|
||||||
def received_session_close(self, data_in:bytes):
|
def received_session_close(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Closes the session when a close session frame is received and
|
Closes the session when a close session frame is received and
|
||||||
the DXCALLSIGN_CRC matches the remote station participating in the session.
|
the DXCALLSIGN_CRC matches the remote station participating in the session.
|
||||||
|
@ -1086,8 +1134,11 @@ class DATA():
|
||||||
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
|
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
|
||||||
if _valid_crc:
|
if _valid_crc:
|
||||||
static.ARQ_SESSION_STATE = 'disconnected'
|
static.ARQ_SESSION_STATE = 'disconnected'
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<X>>[" + str(static.DXCALLSIGN, 'utf-8') + "]", state=static.ARQ_SESSION_STATE)
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<X>>[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
state=static.ARQ_SESSION_STATE)
|
||||||
static.INFO.append("ARQ;SESSION;CLOSE")
|
static.INFO.append("ARQ;SESSION;CLOSE")
|
||||||
|
|
||||||
self.IS_ARQ_SESSION_MASTER = False
|
self.IS_ARQ_SESSION_MASTER = False
|
||||||
|
@ -1107,7 +1158,7 @@ class DATA():
|
||||||
|
|
||||||
self.enqueue_frame_for_tx(connection_frame)
|
self.enqueue_frame_for_tx(connection_frame)
|
||||||
|
|
||||||
def received_session_heartbeat(self, data_in:bytes):
|
def received_session_heartbeat(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -1120,7 +1171,8 @@ class DATA():
|
||||||
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
|
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
|
||||||
if _valid_crc:
|
if _valid_crc:
|
||||||
structlog.get_logger("structlog").debug("[TNC] Received session heartbeat")
|
structlog.get_logger("structlog").debug("[TNC] Received session heartbeat")
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'SESSION-HB', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'SESSION-HB', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp
|
||||||
|
|
||||||
|
@ -1134,7 +1186,8 @@ class DATA():
|
||||||
# ############################################################################################################
|
# ############################################################################################################
|
||||||
# ARQ DATA CHANNEL HANDLER
|
# ARQ DATA CHANNEL HANDLER
|
||||||
# ############################################################################################################
|
# ############################################################################################################
|
||||||
def open_dc_and_transmit(self, data_out:bytes, mode:int, n_frames_per_burst:int, transmission_uuid:str, mycallsign):
|
def open_dc_and_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int, transmission_uuid: str,
|
||||||
|
mycallsign):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -1175,7 +1228,7 @@ class DATA():
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def arq_open_data_channel(self, mode:int, n_frames_per_burst:int, mycallsign):
|
def arq_open_data_channel(self, mode: int, n_frames_per_burst: int, mycallsign):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -1209,9 +1262,12 @@ class DATA():
|
||||||
|
|
||||||
while not static.ARQ_STATE:
|
while not static.ARQ_STATE:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
for attempt in range(1,self.data_channel_max_retries+1):
|
for attempt in range(1, self.data_channel_max_retries + 1):
|
||||||
static.INFO.append("DATACHANNEL;OPENING")
|
static.INFO.append("DATACHANNEL;OPENING")
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | DATA | TX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]", attempt=f"{str(attempt)}/{str(self.data_channel_max_retries)}")
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] ARQ | DATA | TX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN,
|
||||||
|
'utf-8') + "]",
|
||||||
|
attempt=f"{str(attempt)}/{str(self.data_channel_max_retries)}")
|
||||||
|
|
||||||
self.enqueue_frame_for_tx(connection_frame)
|
self.enqueue_frame_for_tx(connection_frame)
|
||||||
|
|
||||||
|
@ -1227,12 +1283,19 @@ class DATA():
|
||||||
|
|
||||||
if attempt == self.data_channel_max_retries:
|
if attempt == self.data_channel_max_retries:
|
||||||
static.INFO.append("DATACHANNEL;FAILED")
|
static.INFO.append("DATACHANNEL;FAILED")
|
||||||
structlog.get_logger("structlog").debug("[TNC] arq_open_data_channel:", transmission_uuid=self.transmission_uuid)
|
|
||||||
jsondata = {"arq":"transmission", "status" :"failed", "uuid" : self.transmission_uuid, "percent" : static.ARQ_TRANSMISSION_PERCENT, "bytesperminute" : static.ARQ_BYTES_PER_MINUTE}
|
structlog.get_logger("structlog").debug("[TNC] arq_open_data_channel:",
|
||||||
|
transmission_uuid=self.transmission_uuid)
|
||||||
|
# print(self.transmission_uuid)
|
||||||
|
jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid,
|
||||||
|
"percent": static.ARQ_TRANSMISSION_PERCENT,
|
||||||
|
"bytesperminute": static.ARQ_BYTES_PER_MINUTE}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
structlog.get_logger("structlog").warning("[TNC] ARQ | TX | DATA [" + str(mycallsign, 'utf-8') + "]>>X<<[" + str(static.DXCALLSIGN, 'utf-8') + "]")
|
structlog.get_logger("structlog").warning(
|
||||||
|
"[TNC] ARQ | TX | DATA [" + str(mycallsign, 'utf-8') + "]>>X<<[" + str(static.DXCALLSIGN,
|
||||||
|
'utf-8') + "]")
|
||||||
self.datachannel_timeout = True
|
self.datachannel_timeout = True
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
@ -1241,9 +1304,9 @@ class DATA():
|
||||||
# open_session frame and can still hear us.
|
# open_session frame and can still hear us.
|
||||||
self.close_session()
|
self.close_session()
|
||||||
return False
|
return False
|
||||||
#sys.exit() # close thread and so connection attempts
|
# sys.exit() # close thread and so connection attempts
|
||||||
|
|
||||||
def arq_received_data_channel_opener(self, data_in:bytes):
|
def arq_received_data_channel_opener(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -1278,7 +1341,8 @@ class DATA():
|
||||||
# updated modes we are listening to
|
# updated modes we are listening to
|
||||||
self.set_listening_modes(self.mode_list[self.speed_level])
|
self.set_listening_modes(self.mode_list[self.speed_level])
|
||||||
|
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
# check if callsign ssid override
|
# check if callsign ssid override
|
||||||
valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4])
|
valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4])
|
||||||
|
@ -1288,7 +1352,9 @@ class DATA():
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
return
|
return
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]", bandwith="wide")
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
bandwith="wide")
|
||||||
|
|
||||||
static.ARQ_STATE = True
|
static.ARQ_STATE = True
|
||||||
static.TNC_STATE = 'BUSY'
|
static.TNC_STATE = 'BUSY'
|
||||||
|
@ -1312,12 +1378,17 @@ class DATA():
|
||||||
|
|
||||||
self.enqueue_frame_for_tx(connection_frame)
|
self.enqueue_frame_for_tx(connection_frame)
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", bandwith="wide", snr=static.SNR)
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
bandwith="wide", snr=static.SNR)
|
||||||
|
|
||||||
# set start of transmission for our statistics
|
# set start of transmission for our statistics
|
||||||
self.rx_start_of_transmission = time.time()
|
self.rx_start_of_transmission = time.time()
|
||||||
|
|
||||||
def arq_received_channel_is_open(self, data_in:bytes):
|
# reset our data channel watchdog
|
||||||
|
self.data_channel_last_received = int(time.time())
|
||||||
|
|
||||||
|
def arq_received_channel_is_open(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called if we received a data channel opener
|
Called if we received a data channel opener
|
||||||
Args:
|
Args:
|
||||||
|
@ -1344,9 +1415,13 @@ class DATA():
|
||||||
self.speed_level = len(self.mode_list) - 1
|
self.speed_level = len(self.mode_list) - 1
|
||||||
structlog.get_logger("structlog").debug("[TNC] high bandwidth mode", modes=self.mode_list)
|
structlog.get_logger("structlog").debug("[TNC] high bandwidth mode", modes=self.mode_list)
|
||||||
|
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR,
|
||||||
|
static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] ARQ | DATA | TX | [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", snr=static.SNR)
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] ARQ | DATA | TX | [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN,
|
||||||
|
'utf-8') + "]",
|
||||||
|
snr=static.SNR)
|
||||||
|
|
||||||
# as soon as we set ARQ_STATE to DATA, transmission starts
|
# as soon as we set ARQ_STATE to DATA, transmission starts
|
||||||
static.ARQ_STATE = True
|
static.ARQ_STATE = True
|
||||||
|
@ -1355,11 +1430,12 @@ class DATA():
|
||||||
static.TNC_STATE = 'IDLE'
|
static.TNC_STATE = 'IDLE'
|
||||||
static.ARQ_STATE = False
|
static.ARQ_STATE = False
|
||||||
static.INFO.append("PROTOCOL;VERSION_MISMATCH")
|
static.INFO.append("PROTOCOL;VERSION_MISMATCH")
|
||||||
structlog.get_logger("structlog").warning("[TNC] protocol version mismatch:", received=protocol_version, own=static.ARQ_PROTOCOL_VERSION)
|
structlog.get_logger("structlog").warning("[TNC] protocol version mismatch:", received=protocol_version,
|
||||||
|
own=static.ARQ_PROTOCOL_VERSION)
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
|
||||||
# ---------- PING
|
# ---------- PING
|
||||||
def transmit_ping(self, dxcallsign:bytes):
|
def transmit_ping(self, dxcallsign: bytes):
|
||||||
"""
|
"""
|
||||||
Funktion for controlling pings
|
Funktion for controlling pings
|
||||||
Args:
|
Args:
|
||||||
|
@ -1372,7 +1448,8 @@ class DATA():
|
||||||
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
||||||
|
|
||||||
static.INFO.append("PING;SENDING")
|
static.INFO.append("PING;SENDING")
|
||||||
structlog.get_logger("structlog").info("[TNC] PING REQ [" + str(self.mycallsign, 'utf-8') + "] >>> [" + str(static.DXCALLSIGN, 'utf-8') + "]" )
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] PING REQ [" + str(self.mycallsign, 'utf-8') + "] >>> [" + str(static.DXCALLSIGN, 'utf-8') + "]")
|
||||||
|
|
||||||
ping_frame = bytearray(14)
|
ping_frame = bytearray(14)
|
||||||
ping_frame[:1] = bytes([210])
|
ping_frame[:1] = bytes([210])
|
||||||
|
@ -1386,7 +1463,7 @@ class DATA():
|
||||||
else:
|
else:
|
||||||
self.enqueue_frame_for_tx(ping_frame)
|
self.enqueue_frame_for_tx(ping_frame)
|
||||||
|
|
||||||
def received_ping(self, data_in:bytes):
|
def received_ping(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called if we received a ping
|
Called if we received a ping
|
||||||
|
|
||||||
|
@ -1398,7 +1475,8 @@ class DATA():
|
||||||
"""
|
"""
|
||||||
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
||||||
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13]))
|
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13]))
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
static.INFO.append("PING;RECEIVING")
|
static.INFO.append("PING;RECEIVING")
|
||||||
|
|
||||||
|
@ -1410,7 +1488,9 @@ class DATA():
|
||||||
# print("ping not for me...")
|
# print("ping not for me...")
|
||||||
return
|
return
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] PING REQ [" + str(mycallsign, 'utf-8') + "] <<< [" + str(static.DXCALLSIGN, 'utf-8') + "]", snr=static.SNR )
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] PING REQ [" + str(mycallsign, 'utf-8') + "] <<< [" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
snr=static.SNR)
|
||||||
|
|
||||||
ping_frame = bytearray(14)
|
ping_frame = bytearray(14)
|
||||||
ping_frame[:1] = bytes([211])
|
ping_frame[:1] = bytes([211])
|
||||||
|
@ -1424,7 +1504,7 @@ class DATA():
|
||||||
else:
|
else:
|
||||||
self.enqueue_frame_for_tx(ping_frame)
|
self.enqueue_frame_for_tx(ping_frame)
|
||||||
|
|
||||||
def received_ping_ack(self, data_in:bytes):
|
def received_ping_ack(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called if a PING ack has been received
|
Called if a PING ack has been received
|
||||||
Args:
|
Args:
|
||||||
|
@ -1436,15 +1516,20 @@ class DATA():
|
||||||
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
||||||
static.DXGRID = bytes(data_in[7:13]).rstrip(b'\x00')
|
static.DXGRID = bytes(data_in[7:13]).rstrip(b'\x00')
|
||||||
|
|
||||||
jsondata = {"type" : "ping", "status" : "ack", "uuid" : str(uuid.uuid4()), "timestamp": int(time.time()), "mycallsign" : str(self.mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'), "dxgrid": str(static.DXGRID, 'utf-8'), "snr": str(static.SNR)}
|
jsondata = {"type": "ping", "status": "ack", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()),
|
||||||
|
"mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'),
|
||||||
|
"dxgrid": str(static.DXGRID, 'utf-8'), "snr": str(static.SNR)}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING-ACK', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING-ACK', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
static.INFO.append("PING;RECEIVEDACK")
|
static.INFO.append("PING;RECEIVEDACK")
|
||||||
|
|
||||||
structlog.get_logger("structlog").info("[TNC] PING ACK [" + str(self.mycallsign, 'utf-8') + "] >|< [" + str(static.DXCALLSIGN, 'utf-8') + "]", snr=static.SNR )
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] PING ACK [" + str(self.mycallsign, 'utf-8') + "] >|< [" + str(static.DXCALLSIGN, 'utf-8') + "]",
|
||||||
|
snr=static.SNR)
|
||||||
static.TNC_STATE = 'IDLE'
|
static.TNC_STATE = 'IDLE'
|
||||||
|
|
||||||
def stop_transmission(self):
|
def stop_transmission(self):
|
||||||
|
@ -1478,9 +1563,10 @@ class DATA():
|
||||||
# ----------- BROADCASTS
|
# ----------- BROADCASTS
|
||||||
def run_beacon(self):
|
def run_beacon(self):
|
||||||
"""
|
"""
|
||||||
Controlling funktion for running a beacon
|
Controlling function for running a beacon
|
||||||
Args:
|
Args:
|
||||||
self:
|
|
||||||
|
self: arq class
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
@ -1512,7 +1598,7 @@ class DATA():
|
||||||
structlog.get_logger("structlog").debug("[TNC] run_beacon: ", exception=e)
|
structlog.get_logger("structlog").debug("[TNC] run_beacon: ", exception=e)
|
||||||
# print(e)
|
# print(e)
|
||||||
|
|
||||||
def received_beacon(self, data_in:bytes):
|
def received_beacon(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called if we received a beacon
|
Called if we received a beacon
|
||||||
Args:
|
Args:
|
||||||
|
@ -1525,19 +1611,23 @@ class DATA():
|
||||||
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
||||||
dxgrid = bytes(data_in[9:13]).rstrip(b'\x00')
|
dxgrid = bytes(data_in[9:13]).rstrip(b'\x00')
|
||||||
|
|
||||||
jsondata = {"type" : "beacon", "status" : "received", "uuid" : str(uuid.uuid4()), "timestamp": int(time.time()), "mycallsign" : str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'), "dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)}
|
jsondata = {"type": "beacon", "status": "received", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()),
|
||||||
|
"mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'),
|
||||||
|
"dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
static.INFO.append("BEACON;RECEIVING")
|
static.INFO.append("BEACON;RECEIVING")
|
||||||
structlog.get_logger("structlog").info("[TNC] BEACON RCVD [" + str(dxcallsign, 'utf-8') + "]["+ str(dxgrid, 'utf-8') +"] ", snr=static.SNR)
|
structlog.get_logger("structlog").info(
|
||||||
helpers.add_to_heard_stations(dxcallsign,dxgrid, 'BEACON', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
"[TNC] BEACON RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR)
|
||||||
|
helpers.add_to_heard_stations(dxcallsign, dxgrid, 'BEACON', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
def transmit_cq(self):
|
def transmit_cq(self):
|
||||||
"""
|
"""
|
||||||
Transmit a CQ
|
Transmit a CQ
|
||||||
Args:
|
Args:
|
||||||
Nothing
|
self
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Nothing
|
Nothing
|
||||||
|
@ -1558,7 +1648,7 @@ class DATA():
|
||||||
else:
|
else:
|
||||||
self.enqueue_frame_for_tx(cq_frame)
|
self.enqueue_frame_for_tx(cq_frame)
|
||||||
|
|
||||||
def received_cq(self, data_in:bytes):
|
def received_cq(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called when we receive a CQ frame
|
Called when we receive a CQ frame
|
||||||
Args:
|
Args:
|
||||||
|
@ -1573,8 +1663,10 @@ class DATA():
|
||||||
# print(dxcallsign)
|
# print(dxcallsign)
|
||||||
dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8")
|
dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8")
|
||||||
static.INFO.append("CQ;RECEIVING")
|
static.INFO.append("CQ;RECEIVING")
|
||||||
structlog.get_logger("structlog").info("[TNC] CQ RCVD [" + str(dxcallsign, 'utf-8') + "]["+ str(dxgrid, 'utf-8') +"] ", snr=static.SNR)
|
structlog.get_logger("structlog").info(
|
||||||
helpers.add_to_heard_stations(dxcallsign, dxgrid, 'CQ CQ CQ', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
"[TNC] CQ RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR)
|
||||||
|
helpers.add_to_heard_stations(dxcallsign, dxgrid, 'CQ CQ CQ', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
if static.RESPOND_TO_CQ:
|
if static.RESPOND_TO_CQ:
|
||||||
self.transmit_qrv()
|
self.transmit_qrv()
|
||||||
|
@ -1583,7 +1675,7 @@ class DATA():
|
||||||
"""
|
"""
|
||||||
Called when we send a QRV frame
|
Called when we send a QRV frame
|
||||||
Args:
|
Args:
|
||||||
data_in:bytes:
|
self
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Nothing
|
Nothing
|
||||||
|
@ -1608,7 +1700,7 @@ class DATA():
|
||||||
else:
|
else:
|
||||||
self.enqueue_frame_for_tx(qrv_frame)
|
self.enqueue_frame_for_tx(qrv_frame)
|
||||||
|
|
||||||
def received_qrv(self, data_in:bytes):
|
def received_qrv(self, data_in: bytes):
|
||||||
"""
|
"""
|
||||||
Called when we receive a QRV frame
|
Called when we receive a QRV frame
|
||||||
Args:
|
Args:
|
||||||
|
@ -1621,16 +1713,20 @@ class DATA():
|
||||||
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
||||||
dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8")
|
dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8")
|
||||||
|
|
||||||
jsondata = {"type" : "qrv", "status" : "received", "uuid" : str(uuid.uuid4()), "timestamp": int(time.time()), "mycallsign" : str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'), "dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)}
|
jsondata = {"type": "qrv", "status": "received", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()),
|
||||||
|
"mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'),
|
||||||
|
"dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)}
|
||||||
json_data_out = json.dumps(jsondata)
|
json_data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(json_data_out)
|
sock.SOCKET_QUEUE.put(json_data_out)
|
||||||
|
|
||||||
static.INFO.append("QRV;RECEIVING")
|
static.INFO.append("QRV;RECEIVING")
|
||||||
structlog.get_logger("structlog").info("[TNC] QRV RCVD [" + str(dxcallsign, 'utf-8') + "]["+ str(dxgrid, 'utf-8') +"] ", snr=static.SNR)
|
structlog.get_logger("structlog").info(
|
||||||
helpers.add_to_heard_stations(dxcallsign,dxgrid, 'QRV', static.SNR, static.FREQ_OFFSET, static.HAMLIB_FREQUENCY)
|
"[TNC] QRV RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR)
|
||||||
|
helpers.add_to_heard_stations(dxcallsign, dxgrid, 'QRV', static.SNR, static.FREQ_OFFSET,
|
||||||
|
static.HAMLIB_FREQUENCY)
|
||||||
|
|
||||||
# ------------ CALUCLATE TRANSFER RATES
|
# ------------ CALUCLATE TRANSFER RATES
|
||||||
def calculate_transfer_rate_rx(self, rx_start_of_transmission:float, receivedbytes:int) -> list:
|
def calculate_transfer_rate_rx(self, rx_start_of_transmission: float, receivedbytes: int) -> list:
|
||||||
"""
|
"""
|
||||||
Calculate transfer rate for received data
|
Calculate transfer rate for received data
|
||||||
Args:
|
Args:
|
||||||
|
@ -1645,7 +1741,8 @@ class DATA():
|
||||||
try:
|
try:
|
||||||
if static.TOTAL_BYTES == 0:
|
if static.TOTAL_BYTES == 0:
|
||||||
static.TOTAL_BYTES = 1
|
static.TOTAL_BYTES = 1
|
||||||
static.ARQ_TRANSMISSION_PERCENT = min(int((receivedbytes*static.ARQ_COMPRESSION_FACTOR / (static.TOTAL_BYTES)) * 100), 100)
|
static.ARQ_TRANSMISSION_PERCENT = min(
|
||||||
|
int((receivedbytes * static.ARQ_COMPRESSION_FACTOR / (static.TOTAL_BYTES)) * 100), 100)
|
||||||
|
|
||||||
transmissiontime = time.time() - self.rx_start_of_transmission
|
transmissiontime = time.time() - self.rx_start_of_transmission
|
||||||
|
|
||||||
|
@ -1678,7 +1775,8 @@ class DATA():
|
||||||
static.ARQ_TRANSMISSION_PERCENT = 0
|
static.ARQ_TRANSMISSION_PERCENT = 0
|
||||||
static.TOTAL_BYTES = 0
|
static.TOTAL_BYTES = 0
|
||||||
|
|
||||||
def calculate_transfer_rate_tx(self, tx_start_of_transmission:float, sentbytes:int, tx_buffer_length:int) -> list:
|
def calculate_transfer_rate_tx(self, tx_start_of_transmission: float, sentbytes: int,
|
||||||
|
tx_buffer_length: int) -> list:
|
||||||
"""
|
"""
|
||||||
Calculate transfer rate for transmission
|
Calculate transfer rate for transmission
|
||||||
Args:
|
Args:
|
||||||
|
@ -1728,7 +1826,7 @@ class DATA():
|
||||||
self.data_frame_ack_received = False
|
self.data_frame_ack_received = False
|
||||||
static.RX_BURST_BUFFER = []
|
static.RX_BURST_BUFFER = []
|
||||||
static.RX_FRAME_BUFFER = b''
|
static.RX_FRAME_BUFFER = b''
|
||||||
self.burst_ack_snr= 255
|
self.burst_ack_snr = 255
|
||||||
|
|
||||||
# reset modem receiving state to reduce cpu load
|
# reset modem receiving state to reduce cpu load
|
||||||
modem.RECEIVE_DATAC1 = False
|
modem.RECEIVE_DATAC1 = False
|
||||||
|
@ -1760,7 +1858,7 @@ class DATA():
|
||||||
|
|
||||||
static.BEACON_PAUSE = False
|
static.BEACON_PAUSE = False
|
||||||
|
|
||||||
def arq_reset_ack(self,state:bool):
|
def arq_reset_ack(self, state: bool):
|
||||||
"""
|
"""
|
||||||
Funktion for resetting acknowledge states
|
Funktion for resetting acknowledge states
|
||||||
Args:
|
Args:
|
||||||
|
@ -1824,14 +1922,18 @@ class DATA():
|
||||||
DATA BURST
|
DATA BURST
|
||||||
"""
|
"""
|
||||||
# IRS SIDE
|
# IRS SIDE
|
||||||
if not static.ARQ_STATE or static.ARQ_SESSION_STATE != 'connected' or static.TNC_STATE != 'BUSY' or not self.is_IRS:
|
# TODO: We need to redesign this part for cleaner state handling
|
||||||
|
# return only if not ARQ STATE and not ARQ SESSION STATE as they are different use cases
|
||||||
|
if not static.ARQ_STATE and static.ARQ_SESSION_STATE != 'connected' or not self.is_IRS:
|
||||||
return
|
return
|
||||||
|
# we want to reach this state only if connected ( == return above not called )
|
||||||
if self.data_channel_last_received + self.time_list[self.speed_level] > time.time():
|
if self.data_channel_last_received + self.time_list[self.speed_level] > time.time():
|
||||||
# print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time())
|
# print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time())
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
structlog.get_logger("structlog").warning("[TNC] Frame timeout", attempt=self.n_retries_per_burst, max_attempts=self.rx_n_max_retries_per_burst, speed_level=self.speed_level)
|
structlog.get_logger("structlog").warning("[TNC] Frame timeout", attempt=self.n_retries_per_burst,
|
||||||
|
max_attempts=self.rx_n_max_retries_per_burst,
|
||||||
|
speed_level=self.speed_level)
|
||||||
self.frame_received_counter = 0
|
self.frame_received_counter = 0
|
||||||
self.burst_nack_counter += 1
|
self.burst_nack_counter += 1
|
||||||
if self.burst_nack_counter >= 2:
|
if self.burst_nack_counter >= 2:
|
||||||
|
@ -1856,7 +1958,6 @@ class DATA():
|
||||||
self.stop_transmission()
|
self.stop_transmission()
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
|
||||||
|
|
||||||
def data_channel_keep_alive_watchdog(self):
|
def data_channel_keep_alive_watchdog(self):
|
||||||
"""
|
"""
|
||||||
watchdog which checks if we are running into a connection timeout
|
watchdog which checks if we are running into a connection timeout
|
||||||
|
@ -1871,7 +1972,8 @@ class DATA():
|
||||||
# pass
|
# pass
|
||||||
else:
|
else:
|
||||||
self.data_channel_last_received = 0
|
self.data_channel_last_received = 0
|
||||||
structlog.get_logger("structlog").info("[TNC] DATA [" + str(self.mycallsign, 'utf-8') + "]<<T>>[" + str(static.DXCALLSIGN, 'utf-8') + "]")
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] DATA [" + str(self.mycallsign, 'utf-8') + "]<<T>>[" + str(static.DXCALLSIGN, 'utf-8') + "]")
|
||||||
static.INFO.append("ARQ;RECEIVING;FAILED")
|
static.INFO.append("ARQ;RECEIVING;FAILED")
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
@ -1885,7 +1987,9 @@ class DATA():
|
||||||
if self.arq_session_last_received + self.arq_session_timeout > time.time():
|
if self.arq_session_last_received + self.arq_session_timeout > time.time():
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
else:
|
else:
|
||||||
structlog.get_logger("structlog").info("[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<T>>[" + str(static.DXCALLSIGN, 'utf-8') + "]")
|
structlog.get_logger("structlog").info(
|
||||||
|
"[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<<T>>[" + str(static.DXCALLSIGN,
|
||||||
|
'utf-8') + "]")
|
||||||
static.INFO.append("ARQ;SESSION;TIMEOUT")
|
static.INFO.append("ARQ;SESSION;TIMEOUT")
|
||||||
self.close_session()
|
self.close_session()
|
||||||
|
|
||||||
|
@ -1901,4 +2005,4 @@ class DATA():
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
def send_test_frame(self):
|
def send_test_frame(self):
|
||||||
modem.MODEM_TRANSMIT_QUEUE.put([12,1,0,[bytearray(126)]])
|
modem.MODEM_TRANSMIT_QUEUE.put([12, 1, 0, [bytearray(126)]])
|
||||||
|
|
|
@ -647,8 +647,8 @@ class RF:
|
||||||
|
|
||||||
snr = round(modem_stats_snr, 1)
|
snr = round(modem_stats_snr, 1)
|
||||||
structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr)
|
structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr)
|
||||||
# print(snr)
|
# static.SNR = np.clip(snr, 0, 255) # limit to max value of 255
|
||||||
static.SNR = np.clip(snr, 0, 255) # limit to max value of 255
|
static.SNR = np.clip(snr, -128, 128) # limit to max value of -128/128 as a possible fix of #188
|
||||||
return static.SNR
|
return static.SNR
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}")
|
structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}")
|
||||||
|
|
Loading…
Reference in a new issue