mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
commit
be7aad423b
47 changed files with 5603 additions and 8539 deletions
128
.github/workflows/build-project-linux.yml
vendored
Normal file
128
.github/workflows/build-project-linux.yml
vendored
Normal file
|
@ -0,0 +1,128 @@
|
|||
name: Linux
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build_linux_release:
|
||||
name: Build Linux release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: ubuntu_tnc
|
||||
generator: Unix Makefiles
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ls-leave-prototype
|
||||
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt install portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
pip3 install psutil
|
||||
pip3 install crcengine
|
||||
pip3 install pyinstaller
|
||||
pip3 install ujson
|
||||
pip3 install pyserial
|
||||
pip3 install numpy
|
||||
pip3 install structlog
|
||||
|
||||
|
||||
#- name: Build Hamlib Python Binding
|
||||
# if: matrix.os == 'ubuntu-latest'
|
||||
# working-directory: tnc
|
||||
# run: |
|
||||
# sudo apt install wig libtool
|
||||
# cd ~
|
||||
# git clone https://github.com/Hamlib/Hamlib.git
|
||||
# cd Hamlib
|
||||
# ./bootstrap
|
||||
# ./configure --with-python-binding PYTHON_VERSION='3.9' --prefix=$HOME/local
|
||||
# make
|
||||
# make install
|
||||
|
||||
|
||||
|
||||
|
||||
- name: Build codec2 Linux
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2 && mkdir build_linux && cd build_linux
|
||||
cmake ../
|
||||
make
|
||||
|
||||
|
||||
- name: Build Linux Daemon
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller freedata.spec
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
|
||||
- name: Compress Linux
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./tnc/dist
|
||||
mkdir compressed
|
||||
zip -r ./compressed/${{ matrix.zip_name }}.zip *
|
||||
|
||||
- name: Upload Ubuntu TNC artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tnc-artifact
|
||||
path: ./tnc/dist/compressed/*
|
||||
|
||||
|
||||
|
||||
- name: Copy TNC to GUI Linux
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
run: |
|
||||
cp -R ./tnc/dist/tnc ./gui/tnc
|
||||
ls -R
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Release TNC
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./tnc/dist/compressed/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: "-p always"
|
||||
|
||||
|
||||
|
117
.github/workflows/build-project-mac.yml
vendored
Normal file
117
.github/workflows/build-project-mac.yml
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
name: macOS
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build_linux_release:
|
||||
name: Build macOS release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-10.15]
|
||||
include:
|
||||
- os: macos-10.15
|
||||
zip_name: mac_tnc
|
||||
generator: Unix Makefiles
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ls-leave-prototype
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
|
||||
|
||||
- name: Install macOS dependencies
|
||||
if: matrix.os == 'macos-10.15'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install psutil
|
||||
pip3 install crcengine
|
||||
pip3 install pyinstaller
|
||||
pip3 install ujson
|
||||
pip3 install pyserial
|
||||
pip3 install numpy
|
||||
pip3 install structlog
|
||||
|
||||
- name: Install Portaudio
|
||||
if: matrix.os == 'macos-10.15'
|
||||
run: |
|
||||
brew install portaudio
|
||||
pip3 install pyaudio
|
||||
|
||||
|
||||
- name: Build codec2 macOS
|
||||
if: matrix.os == 'macos-10.15'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2 && mkdir build_mac && cd build_mac
|
||||
cmake ../
|
||||
make
|
||||
|
||||
|
||||
- name: Build macOS pyinstaller
|
||||
if: matrix.os == 'macos-10.15'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller freedata.spec
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
|
||||
- name: Compress
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./tnc/dist
|
||||
mkdir compressed
|
||||
zip -r ./compressed/${{ matrix.zip_name }}.zip *
|
||||
|
||||
- name: Upload macOS TNC artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tnc-artifact
|
||||
path: ./tnc/dist/compressed/*
|
||||
|
||||
|
||||
|
||||
|
||||
- name: Copy TNC to GUI
|
||||
if: matrix.os == 'macos-10.15'
|
||||
run: |
|
||||
cp -R ./tnc/dist/tnc ./gui/tnc
|
||||
ls -R
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Release TNC
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./tnc/dist/compressed/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: "-p always"
|
||||
|
||||
|
||||
|
115
.github/workflows/build-project-win.yml
vendored
Normal file
115
.github/workflows/build-project-win.yml
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
name: Windows
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
|
||||
build_windows_release:
|
||||
name: Build Windows release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-latest
|
||||
zip_name: windows_tnc
|
||||
generator: Visual Studio 16 2019
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ls-leave-prototype
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install Python dependencies
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install psutil
|
||||
pip install crcengine
|
||||
pip install pyinstaller
|
||||
pip install ujson
|
||||
pip install pyserial
|
||||
pip install numpy
|
||||
pip install structlog
|
||||
pip install colorama
|
||||
# curl.exe --output PyAudio-0.2.11-cp39-cp39-win_amd64.whl --url https://download.lfd.uci.edu/pythonlibs/y2rycu7g/PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
|
||||
- name: Install Pyaudio
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: tnc/lib/pyaudio/windows
|
||||
run: |
|
||||
pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
|
||||
|
||||
- name: Build Windows Daemon and TNC
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller freedata.spec
|
||||
|
||||
- name: Copy TNC to GUI
|
||||
shell: bash
|
||||
run: |
|
||||
cp -R ./tnc/dist/tnc ./gui/tnc
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
- name: Archive TNC
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
directory: ./tnc/dist/tnc
|
||||
path: .
|
||||
#exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
working-directory: tnc
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
|
||||
|
||||
|
||||
#- name: Build codec2
|
||||
# shell: bash
|
||||
# run: |
|
||||
# choco install ninja cmake
|
||||
# ninja --version
|
||||
# cmake --version
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Release TNC
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: "-p always"
|
||||
|
||||
|
308
.github/workflows/build-project.yml
vendored
308
.github/workflows/build-project.yml
vendored
|
@ -1,308 +0,0 @@
|
|||
name: Build/PROJECT
|
||||
on:
|
||||
push:
|
||||
#tags:
|
||||
#- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
build_windows_release:
|
||||
name: Build Windows release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-latest
|
||||
zip_name: windows_tnc
|
||||
generator: Visual Studio 16 2019
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install Python dependencies
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install psutil
|
||||
pip install crcengine
|
||||
pip install pyinstaller
|
||||
pip install ujson
|
||||
pip install pyserial
|
||||
pip install numpy
|
||||
pip install structlog
|
||||
pip install colorama
|
||||
# curl.exe --output PyAudio-0.2.11-cp39-cp39-win_amd64.whl --url https://download.lfd.uci.edu/pythonlibs/y2rycu7g/PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
|
||||
- name: Install Pyaudio
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: tnc/lib/pyaudio/windows
|
||||
run: |
|
||||
pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
|
||||
|
||||
- name: Build Windows Daemon
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller -F --add-binary="lib/codec2/windows/*;lib/codec2/windows/" --add-binary="lib/hamlib/win32/*;lib/hamlib/win32" --add-binary="lib/hamlib/win64/*;lib/hamlib/win64" daemon.py -n daemon
|
||||
|
||||
- name: Build Windows TNC
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller -F --add-binary="lib/codec2/windows/*;lib/codec2/windows/" --add-binary="lib/hamlib/win32/*;lib/hamlib/win32" --add-binary="lib/hamlib/win64/*;lib/hamlib/win64" main.py -n tnc
|
||||
|
||||
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
#- name: Build codec2
|
||||
# shell: bash
|
||||
# run: |
|
||||
# choco install ninja cmake
|
||||
# ninja --version
|
||||
# cmake --version
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Copy TNC and DAEMOn to GUI Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
cp -R ./tnc/dist ./gui/
|
||||
ls -R
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
- name: chmod +x
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ./gui/dist/*.*
|
||||
|
||||
|
||||
- name: Create app bundle
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir gui-bundle
|
||||
cp ./gui/dist/*.* gui-bundle
|
||||
cp ./tnc/dist/daemon.exe gui-bundle
|
||||
cp ./tnc/dist/tnc.exe gui-bundle
|
||||
chmod +x ./gui-bundle/tnc
|
||||
chmod +x ./gui-bundle/daemon
|
||||
cd ./gui-bundle
|
||||
ls -R
|
||||
|
||||
- name: Archive Release
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: 'FreeDATA_windows.zip'
|
||||
directory: ./gui-bundle/
|
||||
path: .
|
||||
#exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: Upload Windows GUI Bundle artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gui-bundle-windows-artifact
|
||||
path: ./gui-bundle/FreeDATA_windows.zip
|
||||
|
||||
|
||||
build_linux_release:
|
||||
name: Build Linux release
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: ubuntu_tnc
|
||||
generator: Unix Makefiles
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt install portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
pip3 install psutil
|
||||
pip3 install crcengine
|
||||
pip3 install pyinstaller
|
||||
pip3 install ujson
|
||||
pip3 install pyserial
|
||||
pip3 install numpy
|
||||
pip3 install structlog
|
||||
|
||||
|
||||
#- name: Build Hamlib Python Binding
|
||||
# if: matrix.os == 'ubuntu-latest'
|
||||
# working-directory: tnc
|
||||
# run: |
|
||||
# sudo apt install wig libtool
|
||||
# cd ~
|
||||
# git clone https://github.com/Hamlib/Hamlib.git
|
||||
# cd Hamlib
|
||||
# ./bootstrap
|
||||
# ./configure --with-python-binding PYTHON_VERSION='3.9' --prefix=$HOME/local
|
||||
# make
|
||||
# make install
|
||||
|
||||
|
||||
|
||||
|
||||
- name: Build codec2 Linux
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2 && mkdir build_linux && cd build_linux
|
||||
cmake ../
|
||||
make
|
||||
|
||||
|
||||
- name: Build Linux Daemon
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
working-directory: tnc
|
||||
run: |
|
||||
pyinstaller freedata.spec
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
|
||||
- name: Compress Linux
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./tnc/dist
|
||||
mkdir compressed
|
||||
zip -r ./compressed/${{ matrix.zip_name }}.zip *
|
||||
|
||||
- name: Upload Ubuntu TNC artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: tnc-artifact
|
||||
path: ./tnc/dist/compressed/*
|
||||
|
||||
- name: Copy TNC to GUI Linux
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
run: |
|
||||
cp -R ./tnc/dist/tnc ./gui/tnc
|
||||
ls -R
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
- name: chmod +x
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ./gui/dist/*.AppImage
|
||||
|
||||
|
||||
- name: Create app bundle
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir gui-bundle
|
||||
cp ./gui/dist/*.AppImage gui-bundle
|
||||
cp -R ./tnc/dist/tnc gui-bundle
|
||||
chmod +x ./gui-bundle/tnc/tnc
|
||||
chmod +x ./gui-bundle/tnc/daemon
|
||||
cd ./gui-bundle
|
||||
zip -r FreeDATA_linux.zip .
|
||||
ls -R
|
||||
|
||||
|
||||
|
||||
|
||||
- name: Upload Ubuntu GUI Bundle artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gui-bundle-ubuntu-artifact
|
||||
path: ./gui-bundle/FreeDATA_linux.zip
|
||||
|
||||
|
||||
|
||||
|
||||
release:
|
||||
name: Upload Release
|
||||
needs: [build_linux_release, build_windows_release]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
shell: bash
|
||||
run: |
|
||||
ls -R
|
||||
|
||||
|
||||
- name: Release
|
||||
uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
#uses: softprops/action-gh-release@v1
|
||||
#if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/DJ2LS/FreeDATA/releases/48616289/assets{?name,label} # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
|
||||
release_id: 48616289 # same as above (id can just be taken out the upload_url, it's used to find old releases)
|
||||
asset_path: ./gui-bundle-ubuntu-artifact/FreeDATA_linux.zip # path to archive to upload
|
||||
asset_name: FreeDATA_linux-nightly-$$.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
#files: |
|
||||
# ./gui-bundle-ubuntu-artifact/codec2-FreeDATA_ubuntu.zip
|
||||
# ./tnc-artifact/ubuntu_tnc.zip
|
||||
# LICENSE
|
4
.stignore
Normal file
4
.stignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
tnc/__pycache__
|
||||
tnc/daemon.log
|
||||
tnc/tnc.log
|
||||
gui/node_modules
|
11
README.md
11
README.md
|
@ -4,9 +4,16 @@ My attempt to create a free and open-source TNC with a GUI for [codec2](https://
|
|||
[mailing-list](https://groups.io/g/freedata)
|
||||
|
||||
## Under development
|
||||
|
||||
![Build Windows](https://github.com/DJ2LS/FreeDATA/actions/workflows/build-project-win.yml/badge.svg)
|
||||
![Build Linux](https://github.com/DJ2LS/FreeDATA/actions/workflows/build-project-linux.yml/badge.svg)
|
||||
![Build macOS](https://github.com/DJ2LS/FreeDATA/actions/workflows/build-project-mac.yml/badge.svg)
|
||||
|
||||
Please keep in mind, that this project is still a prototype with many issues which need to be solved.
|
||||
Build steps for other OS than Ubuntu are provided, but not fully working, yet.
|
||||
|
||||
Please check the [Releases](https://github.com/DJ2LS/FreeDATA/releases) section for downloading nightly builds
|
||||
|
||||
## Preview
|
||||
![preview](https://github.com/DJ2LS/FreeDATA/blob/main/documentation/FreeDATA_preview.gif?raw=true "Preview")
|
||||
|
||||
|
@ -20,7 +27,7 @@ xssfox : https://github.com/xssfox/freedv-tnc
|
|||
## Running the Ubuntu app bundle
|
||||
Download the latest developer release from the releases section, unpack it and just start the ".AppImage file". No more dependencies
|
||||
|
||||
## Manual installation
|
||||
Please check the [wiki](https://github.com/DJ2LS/FreeDATA/wiki) for installation instructions
|
||||
## Installation
|
||||
Please check the [wiki](https://wiki.freedata.app) for installation instructions
|
||||
|
||||
|
||||
|
|
BIN
documentation/cube.xcf
Normal file
BIN
documentation/cube.xcf
Normal file
Binary file not shown.
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"tnc_host" : "127.0.0.1",
|
||||
"tnc_port" : "3000",
|
||||
"daemon_host" : "127.0.0.1",
|
||||
"daemon_port" : "3001",
|
||||
"mycall" : "AA0AA",
|
||||
"mygrid" : "AA11ea"
|
||||
}
|
168
gui/daemon.js
168
gui/daemon.js
|
@ -3,6 +3,10 @@ const path = require('path')
|
|||
const {
|
||||
ipcRenderer
|
||||
} = require('electron')
|
||||
const log = require('electron-log');
|
||||
const daemonLog = log.scope('daemon');
|
||||
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config")
|
||||
|
@ -11,16 +15,17 @@ var configPath = path.join(configFolder, 'config.json')
|
|||
const config = require(configPath);
|
||||
|
||||
var daemon = new net.Socket();
|
||||
var msg = ''; // Current message, per connection.
|
||||
var socketchunk = ''; // Current message, per connection.
|
||||
|
||||
setTimeout(connectDAEMON, 500)
|
||||
|
||||
function connectDAEMON() {
|
||||
|
||||
console.log('connecting to DAEMON...')
|
||||
|
||||
daemonLog.info('connecting to daemon');
|
||||
|
||||
//clear message buffer after reconnecting or inital connection
|
||||
msg = '';
|
||||
socketchunk = '';
|
||||
|
||||
if (config.tnclocation == 'localhost') {
|
||||
daemon.connect(3001, '127.0.0.1')
|
||||
|
@ -32,25 +37,48 @@ function connectDAEMON() {
|
|||
//client.setTimeout(5000);
|
||||
}
|
||||
|
||||
daemon.on('connect', function(data) {
|
||||
console.log('DAEMON connection established')
|
||||
daemon.on('connect', function(err) {
|
||||
|
||||
daemonLog.info('daemon connection established');
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
|
||||
})
|
||||
|
||||
daemon.on('error', function(data) {
|
||||
console.log('DAEMON connection error');
|
||||
setTimeout(connectDAEMON, 2000)
|
||||
daemon.on('error', function(err) {
|
||||
daemonLog.error('daemon connection error');
|
||||
daemonLog.error(err)
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 1000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
*/
|
||||
|
||||
daemon.on('end', function(data) {
|
||||
console.log('DAEMON connection ended');
|
||||
setTimeout(connectDAEMON, 2000)
|
||||
|
||||
daemonLog.warn('daemon connection ended');
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 500)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
|
||||
//exports.writeCommand = function(command){
|
||||
|
@ -79,50 +107,91 @@ writeDaemonCommand = function(command) {
|
|||
|
||||
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
|
||||
|
||||
daemon.on('data', function(data) {
|
||||
daemon.on('data', function(socketdata) {
|
||||
|
||||
data = data.toString('utf8'); /* convert data to string */
|
||||
msg += data.toString('utf8'); /*append data to buffer so we can stick long data together */
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
/* check if we reached an EOF, if true, clear buffer and parse JSON data */
|
||||
if (data.endsWith('}')) {
|
||||
/*console.log(msg)*/
|
||||
try {
|
||||
/*console.log(msg)*/
|
||||
data = JSON.parse(msg)
|
||||
} catch (e) {
|
||||
console.log(e); /* "SyntaxError */
|
||||
|
||||
socketdata = socketdata.toString('utf8'); // convert data to string
|
||||
socketchunk += socketdata// append data to buffer so we can stick long data together
|
||||
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
|
||||
var data = ''
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
data = JSON.parse(socketchunk[0])
|
||||
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === ''){
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
msg = '';
|
||||
/*console.log("EOF detected!")*/
|
||||
|
||||
if (data['COMMAND'] == 'DAEMON_STATE') {
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
|
||||
//check if data is not empty
|
||||
if(socketchunk[i].length > 0){
|
||||
|
||||
//try to parse JSON
|
||||
try {
|
||||
|
||||
data = JSON.parse(socketchunk[i])
|
||||
|
||||
} catch (e) {
|
||||
console.log(e); // "SyntaxError
|
||||
daemonLog.error(e);
|
||||
daemonLog.debug(socketchunk[i])
|
||||
socketchunk = ''
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (data['command'] == 'daemon_state') {
|
||||
let Data = {
|
||||
input_devices: data['INPUT_DEVICES'],
|
||||
output_devices: data['OUTPUT_DEVICES'],
|
||||
python_version: data['PYTHON_VERSION'],
|
||||
hamlib_version: data['HAMLIB_VERSION'],
|
||||
serial_devices: data['SERIAL_DEVICES'],
|
||||
tnc_running_state: data['DAEMON_STATE'][0]['STATUS'],
|
||||
ram_usage: data['RAM'],
|
||||
cpu_usage: data['CPU'],
|
||||
version: data['VERSION'],
|
||||
input_devices: data['input_devices'],
|
||||
output_devices: data['output_devices'],
|
||||
python_version: data['python_version'],
|
||||
hamlib_version: data['hamlib_version'],
|
||||
serial_devices: data['serial_devices'],
|
||||
tnc_running_state: data['daemon_state'][0]['status'],
|
||||
ram_usage: data['ram'],
|
||||
cpu_usage: data['cpu'],
|
||||
version: data['version'],
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-state', Data);
|
||||
}
|
||||
|
||||
if (data['COMMAND'] == 'TEST_HAMLIB') {
|
||||
if (data['command'] == 'test_hamlib') {
|
||||
let Data = {
|
||||
hamlib_result: data['RESULT'],
|
||||
hamlib_result: data['result'],
|
||||
|
||||
};
|
||||
ipcRenderer.send('request-update-hamlib-test', Data);
|
||||
}
|
||||
|
||||
|
||||
////// check if EOF ...
|
||||
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = '';
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
|
@ -133,17 +202,17 @@ function hexToBytes(hex) {
|
|||
|
||||
exports.getDaemonState = function() {
|
||||
//function getDaemonState(){
|
||||
command = '{"type" : "GET", "command" : "DAEMON_STATE"}'
|
||||
command = '{"type" : "get", "command" : "daemon_state"}'
|
||||
writeDaemonCommand(command)
|
||||
}
|
||||
|
||||
// START TNC
|
||||
// ` `== multi line string
|
||||
|
||||
exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, devicename, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port) {
|
||||
exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, devicename, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port, enable_fft, enable_scatter, low_bandwith_mode) {
|
||||
var json_command = JSON.stringify({
|
||||
type: 'SET',
|
||||
command: 'STARTTNC',
|
||||
type: 'set',
|
||||
command: 'start_tnc',
|
||||
parameter: [{
|
||||
mycall: mycall,
|
||||
mygrid: mygrid,
|
||||
|
@ -159,18 +228,21 @@ exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, de
|
|||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip
|
||||
rigctld_ip: rigctld_ip,
|
||||
enable_scatter: enable_scatter,
|
||||
enable_fft: enable_fft,
|
||||
low_bandwith_mode : low_bandwith_mode
|
||||
}]
|
||||
})
|
||||
|
||||
//console.log(json_command)
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command)
|
||||
|
||||
}
|
||||
|
||||
// STOP TNC
|
||||
exports.stopTNC = function() {
|
||||
command = '{"type" : "SET", "command": "STOPTNC" , "parameter": "---" }'
|
||||
command = '{"type" : "set", "command": "stop_tnc" , "parameter": "---" }'
|
||||
writeDaemonCommand(command)
|
||||
}
|
||||
|
||||
|
@ -178,8 +250,8 @@ exports.stopTNC = function() {
|
|||
exports.testHamlib = function(radiocontrol, devicename, deviceport, serialspeed, pttprotocol, pttport, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port) {
|
||||
|
||||
var json_command = JSON.stringify({
|
||||
type: 'GET',
|
||||
command: 'TEST_HAMLIB',
|
||||
type: 'get',
|
||||
command: 'test_hamlib',
|
||||
parameter: [{
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
|
@ -194,7 +266,7 @@ exports.testHamlib = function(radiocontrol, devicename, deviceport, serialspeed,
|
|||
rigctld_ip: rigctld_ip
|
||||
}]
|
||||
})
|
||||
console.log(json_command)
|
||||
daemonLog.debug(json_command);
|
||||
writeDaemonCommand(json_command)
|
||||
}
|
||||
|
||||
|
@ -202,13 +274,13 @@ exports.testHamlib = function(radiocontrol, devicename, deviceport, serialspeed,
|
|||
|
||||
//Save myCall
|
||||
exports.saveMyCall = function(callsign) {
|
||||
command = '{"type" : "SET", "command": "MYCALLSIGN" , "parameter": "' + callsign + '", "timestamp" : "' + Date.now() + '"}'
|
||||
command = '{"type" : "set", "command": "mycallsign" , "parameter": "' + callsign + '"}'
|
||||
writeDaemonCommand(command)
|
||||
}
|
||||
|
||||
// Save myGrid
|
||||
exports.saveMyGrid = function(grid) {
|
||||
command = '{"type" : "SET", "command": "MYGRID" , "parameter": "' + grid + '", "timestamp" : "' + Date.now() + '"}'
|
||||
command = '{"type" : "set", "command": "mygrid" , "parameter": "' + grid + '"}'
|
||||
writeDaemonCommand(command)
|
||||
}
|
||||
|
||||
|
|
442
gui/main.js
442
gui/main.js
|
@ -1,59 +1,112 @@
|
|||
const {
|
||||
app,
|
||||
BrowserWindow,
|
||||
ipcMain
|
||||
} = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
ipcMain,
|
||||
dialog,
|
||||
shell
|
||||
} = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const exec = require('child_process').spawn;
|
||||
const log = require('electron-log');
|
||||
const mainLog = log.scope('main');
|
||||
const daemonProcessLog = log.scope('freedata-daemon');
|
||||
|
||||
const sysInfo = log.scope('system information');
|
||||
sysInfo.info("SYSTEM INFORMATION ----------------------------- ");
|
||||
sysInfo.info("APP VERSION : " + app.getVersion());
|
||||
sysInfo.info("PLATFORM : " + os.platform());
|
||||
sysInfo.info("ARCHITECTURE: " + os.arch());
|
||||
sysInfo.info("FREE MEMORY: " + os.freemem());
|
||||
sysInfo.info("TOTAL MEMORY: " + os.totalmem());
|
||||
sysInfo.info("LOAD AVG : " + os.loadavg());
|
||||
sysInfo.info("RELEASE : " + os.release());
|
||||
sysInfo.info("TYPE : " + os.type());
|
||||
sysInfo.info("VERSION : " + os.version());
|
||||
sysInfo.info("UPTIME : " + os.uptime());
|
||||
|
||||
|
||||
|
||||
app.setName("FreeDATA");
|
||||
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config")
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, 'config.json')
|
||||
var configPath = path.join(configFolder, 'config.json');
|
||||
|
||||
// create config folder if not exists
|
||||
if (!fs.existsSync(configFolder)) {
|
||||
fs.mkdirSync(configFolder);
|
||||
}
|
||||
|
||||
// create config file if not exists
|
||||
var configContent = `
|
||||
{
|
||||
"tnc_host": "127.0.0.1",
|
||||
"tnc_port": "3000",
|
||||
"daemon_host": "127.0.0.1",
|
||||
"daemon_port": "3001",
|
||||
"mycall": "AA0AA",
|
||||
"mygrid": "JN40aa",
|
||||
"deviceid": "RIG_MODEL_DUMMY_NOVFO",
|
||||
"deviceport": "/dev/ttyACM1",
|
||||
"serialspeed": "9600",
|
||||
"ptt": "USB",
|
||||
"spectrum": "waterfall",
|
||||
"tnclocation": "localhost",
|
||||
"stop_bits" : "1",
|
||||
"data_bits" : "8",
|
||||
"handshake" : "None",
|
||||
"radiocontrol" : "direct",
|
||||
"deviceport_rigctl" : "3",
|
||||
"deviceid_rigctl" : "3",
|
||||
"serialspeed_rigctl" : "9600",
|
||||
"pttprotocol_rigctl" : "USB",
|
||||
"rigctld_port" : "4532",
|
||||
"rigctld_ip" : "127.0.0.1"
|
||||
// create config file if not exists with defaults
|
||||
const configDefaultSettings = '{\
|
||||
"tnc_host": "127.0.0.1",\
|
||||
"tnc_port": "3000",\
|
||||
"daemon_host": "127.0.0.1",\
|
||||
"daemon_port": "3001",\
|
||||
"mycall": "AA0AA-0",\
|
||||
"mygrid": "JN40aa",\
|
||||
"deviceid": "RIG_MODEL_DUMMY_NOVFO",\
|
||||
"deviceport": "/dev/ttyACM1",\
|
||||
"serialspeed_direct": "9600",\
|
||||
"spectrum": "waterfall",\
|
||||
"tnclocation": "localhost",\
|
||||
"stop_bits_direct" : "1",\
|
||||
"data_bits_direct" : "8",\
|
||||
"handshake_direct" : "None",\
|
||||
"radiocontrol" : "disabled",\
|
||||
"deviceport_rigctl" : "3",\
|
||||
"deviceid_rigctl" : "3",\
|
||||
"serialspeed_rigctl" : "9600",\
|
||||
"pttprotocol_direct" : "USB",\
|
||||
"pttprotocol_rigctl" : "USB",\
|
||||
"rigctld_port" : "4532",\
|
||||
"rigctld_ip" : "127.0.0.1",\
|
||||
"enable_scatter" : "False",\
|
||||
"enable_fft" : "False",\
|
||||
"low_bandwith_mode" : "False",\
|
||||
"theme" : "default",\
|
||||
"screen_height" : 430,\
|
||||
"screen_width" : 1050,\
|
||||
"update_channel" : "latest",\
|
||||
"beacon_interval" : 5,\
|
||||
"received_files_folder" : "None"\
|
||||
}';
|
||||
|
||||
}
|
||||
`;
|
||||
if (!fs.existsSync(configPath)) {
|
||||
fs.writeFileSync(configPath, configContent)
|
||||
fs.writeFileSync(configPath, configDefaultSettings)
|
||||
}
|
||||
|
||||
// load settings
|
||||
var config = require(configPath);
|
||||
|
||||
//config validation
|
||||
// check running config against default config.
|
||||
// if parameter not exists, add it to running config to prevent errors
|
||||
sysInfo.info("CONFIG VALIDATION ----------------------------- ");
|
||||
|
||||
var parsedConfig = JSON.parse(configDefaultSettings);
|
||||
for (key in parsedConfig) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
sysInfo.info("FOUND SETTTING [" + key + "]: " + config[key]);
|
||||
} else {
|
||||
sysInfo.error("MISSING SETTTING [" + key + "] : " + parsedConfig[key]);
|
||||
config[key] = parsedConfig[key];
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
}
|
||||
sysInfo.info("------------------------------------------ ");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
var chatDB = path.join(configFolder, 'chatDB.json')
|
||||
// create chat database file if not exists
|
||||
var configContent = `
|
||||
const configContentChatDB = `
|
||||
{ "chatDB" : [{
|
||||
"id" : "00000000",
|
||||
"timestamp" : 1234566,
|
||||
|
@ -65,9 +118,9 @@ var configContent = `
|
|||
}
|
||||
`;
|
||||
if (!fs.existsSync(chatDB)) {
|
||||
fs.writeFileSync(chatDB, configContent)
|
||||
fs.writeFileSync(chatDB, configContentChatDB);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
@ -88,25 +141,29 @@ fs.mkdir(receivedFilesFolder, {
|
|||
|
||||
|
||||
|
||||
const config = require(configPath);
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
|
||||
|
||||
let win = null;
|
||||
let data = null;
|
||||
let logViewer = null;
|
||||
var daemonProcess = null;
|
||||
|
||||
function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 1050,
|
||||
height: 430,
|
||||
width: config.screen_width,
|
||||
height: config.screen_height,
|
||||
autoHideMenuBar: true,
|
||||
icon: __dirname + '/src/icon_cube_border.png',
|
||||
icon: 'src/img/icon.png',
|
||||
webPreferences: {
|
||||
//preload: path.join(__dirname, 'preload-main.js'),
|
||||
preload: require.resolve('./preload-main.js'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: false, //https://stackoverflow.com/questions/53390798/opening-new-window-electron/53393655 https://github.com/electron/remote
|
||||
enableRemoteModule: false,
|
||||
sandbox: false
|
||||
//https://stackoverflow.com/questions/53390798/opening-new-window-electron/53393655
|
||||
//https://github.com/electron/remote
|
||||
}
|
||||
})
|
||||
// hide menu bar
|
||||
|
@ -132,90 +189,219 @@ function createWindow() {
|
|||
}
|
||||
})
|
||||
|
||||
chat.loadFile('src/chat-module.html')
|
||||
chat.setMenuBarVisibility(false)
|
||||
chat.loadFile('src/chat-module.html');
|
||||
chat.setMenuBarVisibility(false);
|
||||
|
||||
|
||||
logViewer = new BrowserWindow({
|
||||
height: 900,
|
||||
width: 600,
|
||||
show: false,
|
||||
parent: win,
|
||||
webPreferences: {
|
||||
preload: require.resolve('./preload-log.js'),
|
||||
nodeIntegration: true,
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
logViewer.loadFile('src/log-module.html');
|
||||
logViewer.setMenuBarVisibility(false);
|
||||
|
||||
// Emitted when the window is closed.
|
||||
logViewer.on('close', function(evt) {
|
||||
evt.preventDefault();
|
||||
logViewer.hide();
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on('closed', function() {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
chat = null;
|
||||
logViewer = null;
|
||||
})
|
||||
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
|
||||
log.transports.file.level = "debug"
|
||||
autoUpdater.logger = log.scope('updater');
|
||||
|
||||
autoUpdater.channel = config.update_channel
|
||||
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
//autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
|
||||
chat.on('closed', function () {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
|
||||
})
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/44258831/only-hide-the-window-when-closing-it-electron
|
||||
chat.on('close', function(evt) {
|
||||
evt.preventDefault();
|
||||
chat.hide()
|
||||
chat.hide();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
createWindow();
|
||||
|
||||
// start daemon by checking os
|
||||
// https://stackoverflow.com/a/5775120
|
||||
console.log("Trying to start daemon binary")
|
||||
mainLog.info('Starting freedata-daemon binary');
|
||||
|
||||
if(os.platform()=='linux' || os.platform()=='darwin'){
|
||||
daemonProcess = exec('./tnc/daemon', function callback(err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.log(os.platform())
|
||||
console.error(err)
|
||||
console.error("Can't start daemon binary");
|
||||
console.error("--> this is only working with the app bundle and a precompiled binaries");
|
||||
return;
|
||||
}
|
||||
console.log(stdout);
|
||||
if(os.platform()=='darwin'){
|
||||
daemonProcess = exec(path.join(process.resourcesPath, 'tnc', 'freedata-daemon'), [],
|
||||
{
|
||||
cwd: path.join(process.resourcesPath, 'tnc'),
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
process.resourcesPath -->
|
||||
/tmp/.mount_FreeDAUQYfKb/resources
|
||||
|
||||
__dirname -->
|
||||
/tmp/.mount_FreeDAUQYfKb/resources/app.asar
|
||||
*/
|
||||
|
||||
if(os.platform()=='linux'){
|
||||
|
||||
/*
|
||||
var folder = path.join(process.resourcesPath, 'tnc');
|
||||
//var folder = path.join(__dirname, 'extraResources', 'tnc');
|
||||
console.log(folder);
|
||||
fs.readdir(folder, (err, files) => {
|
||||
console.log(files);
|
||||
});
|
||||
*/
|
||||
|
||||
daemonProcess = exec(path.join(process.resourcesPath, 'tnc', 'freedata-daemon'), [],
|
||||
{
|
||||
cwd: path.join(process.resourcesPath, 'tnc'),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if(os.platform()=='win32' || os.platform()=='win64'){
|
||||
daemonProcess = exec('./tnc/daemon.exe', function callback(err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.log(os.platform())
|
||||
console.error(err)
|
||||
console.error("Can't start daemon binary");
|
||||
console.error("--> this is only working with the app bundle and a precompiled binaries");
|
||||
return;
|
||||
}
|
||||
console.log(stdout);
|
||||
// for windows the relative path via path.join(__dirname) is not needed for some reason
|
||||
//daemonProcess = exec('\\tnc\\daemon.exe', [])
|
||||
|
||||
daemonProcess = exec(path.join(process.resourcesPath, 'tnc', 'freedata-daemon.exe'), [],
|
||||
{
|
||||
cwd: path.join(process.resourcesPath, 'tnc'),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// return process messages
|
||||
|
||||
daemonProcess.on('error', (err) => {
|
||||
daemonProcessLog.error(`error when starting daemon: ${err}`);
|
||||
});
|
||||
|
||||
daemonProcess.on('message', (data) => {
|
||||
daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
|
||||
daemonProcess.stdout.on('data', (data) => {
|
||||
daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
daemonProcess.stderr.on('data', (data) => {
|
||||
daemonProcessLog.info(`${data}`);
|
||||
|
||||
let arg = {
|
||||
entry: `${data}`
|
||||
};
|
||||
// send info to log only if log screen available
|
||||
// it seems an error occurs when updating
|
||||
if (logViewer !== null && logViewer !== ''){
|
||||
logViewer.webContents.send('action-update-log', arg);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
daemonProcess.on('close', (code) => {
|
||||
daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
createWindow();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// kill daemon process
|
||||
daemonProcess.kill('SIGINT');
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
|
||||
// closing the tnc binary if not closed when closing application and also our daemon which has been started by the gui
|
||||
try {
|
||||
daemonProcess.kill();
|
||||
} catch (e) {
|
||||
mainLog.error(e)
|
||||
}
|
||||
|
||||
|
||||
mainLog.warn('closing tnc');
|
||||
|
||||
if(os.platform()=='win32' || os.platform()=='win64'){
|
||||
exec('Taskkill', ['/IM', 'freedata-tnc.exe', '/F'])
|
||||
}
|
||||
|
||||
if(os.platform()=='linux'){
|
||||
|
||||
exec('pkill', ['-9', 'freedata-tnc'])
|
||||
|
||||
// on macOS we need to kill the daemon as well. If we are not doing this,
|
||||
// the daemon wont startup again because the socket is already in use
|
||||
//for some reason killing the daemon is killing our screen on Ubuntu..it seems theres another "daemon" out there...
|
||||
exec('pkill', ['-9', 'freedata-daemon'])
|
||||
}
|
||||
|
||||
if(os.platform()=='darwin'){
|
||||
|
||||
exec('pkill', ['-9', 'freedata-tnc'])
|
||||
|
||||
// on macOS we need to kill the daemon as well. If we are not doing this,
|
||||
// the daemon wont startup again because the socket is already in use
|
||||
//for some reason killing the daemon is killing our screen on Ubuntu..it seems theres another "daemon" out there...
|
||||
exec('pkill', ['-9', 'freedata-daemon'])
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
*/
|
||||
mainLog.warn('quitting app');
|
||||
app.quit();
|
||||
|
||||
})
|
||||
|
||||
// IPC HANDLER
|
||||
|
||||
ipcMain.on('request-show-chat-window', (event, arg) => {
|
||||
chat.show()
|
||||
chat.show();
|
||||
});
|
||||
|
||||
|
||||
|
@ -244,6 +430,9 @@ ipcMain.on('request-update-hamlib-test', (event, arg) => {
|
|||
|
||||
|
||||
|
||||
ipcMain.on('request-update-tnc-connection', (event, arg) => {
|
||||
win.webContents.send('action-update-tnc-connection', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('request-update-daemon-connection', (event, arg) => {
|
||||
win.webContents.send('action-update-daemon-connection', arg);
|
||||
|
@ -257,6 +446,99 @@ ipcMain.on('request-update-rx-buffer', (event, arg) => {
|
|||
win.webContents.send('action-update-rx-buffer', arg);
|
||||
});
|
||||
|
||||
/*
|
||||
ipcMain.on('request-update-rx-msg-buffer', (event, arg) => {
|
||||
chat.webContents.send('action-update-rx-msg-buffer', arg);
|
||||
});
|
||||
*/
|
||||
ipcMain.on('request-new-msg-received', (event, arg) => {
|
||||
chat.webContents.send('action-new-msg-received', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('request-open-tnc-log', (event) => {
|
||||
logViewer.show();
|
||||
});
|
||||
|
||||
//folder selector
|
||||
ipcMain.on('get-folder-path',(event,data)=>{
|
||||
dialog.showOpenDialog({defaultPath: path.join(__dirname, '../assets/'),
|
||||
buttonLabel: 'Select folder', properties: ['openDirectory']}).then(folderPaths => {
|
||||
win.webContents.send('return-folder-paths', {path: folderPaths,})
|
||||
});
|
||||
});
|
||||
|
||||
//open folder
|
||||
ipcMain.on('open-folder',(event,data)=>{
|
||||
shell.showItemInFolder(data.path)
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// LISTENER FOR UPDATER EVENTS
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
mainLog.info('update available');
|
||||
|
||||
let arg = {
|
||||
status: "update-available",
|
||||
info: info
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
mainLog.info('update not available');
|
||||
let arg = {
|
||||
status: "update-not-available",
|
||||
info: info
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
});
|
||||
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
mainLog.info('update downloaded');
|
||||
let arg = {
|
||||
status: "update-downloaded",
|
||||
info: info
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
// we need to call this at this point.
|
||||
// if an update is available and we are force closing the app
|
||||
// the entire screen crashes...
|
||||
mainLog.info('quit application and install update');
|
||||
autoUpdater.quitAndInstall();
|
||||
|
||||
|
||||
});
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
mainLog.info('checking for update');
|
||||
let arg = {
|
||||
status: "checking-for-update",
|
||||
version: app.getVersion()
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
let arg = {
|
||||
status: "download-progress",
|
||||
progress: progress
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (error) => {
|
||||
mainLog.info('update error');
|
||||
let arg = {
|
||||
status: "error",
|
||||
progress: error
|
||||
};
|
||||
win.webContents.send('action-updater', arg);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
6170
gui/package-lock.json
generated
6170
gui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.2-alpha",
|
||||
"description": "FreeDATA ",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
@ -8,12 +8,12 @@
|
|||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/DJ2LS/FreeDATA.git"
|
||||
"url": "https://github.com/DJ2LS/FreeDATA.git"
|
||||
},
|
||||
"keywords": [
|
||||
"TNC",
|
||||
|
@ -26,15 +26,64 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/DJ2LS/FreeDATA/issues"
|
||||
},
|
||||
"homepage": "https://github.com/DJ2LS/FreeDATA#readme",
|
||||
"homepage": "https://freedata.app",
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.1.0",
|
||||
"bootstrap-icons": "^1.8.1",
|
||||
"bootswatch": "^5.1.3",
|
||||
"chart.js": "^3.5.1",
|
||||
"chartjs-plugin-annotation": "^1.0.2",
|
||||
"qth-locator": "^2.1.0"
|
||||
"electron-log": "^4.4.6",
|
||||
"electron-updater": "^5.0.0",
|
||||
"pouchdb": "^7.2.2",
|
||||
"qth-locator": "^2.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^15.0.0",
|
||||
"electron-builder": "^22.11.7"
|
||||
"electron": "^17.0.0",
|
||||
"electron-builder": "^22.14.13"
|
||||
},
|
||||
"build": {
|
||||
"productName": "FreeDATA",
|
||||
"appId": "app.freedata",
|
||||
"directories": {
|
||||
"buildResources": "src/img",
|
||||
"output": "dist"
|
||||
},
|
||||
"dmg": {
|
||||
"icon": "src/img/icon.png",
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"icon": "src/img/icon.png",
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "src/img/icon.png",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"releaseType": "release"
|
||||
},
|
||||
"extraResources": [
|
||||
"./tnc/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,87 +3,293 @@ const {
|
|||
ipcRenderer
|
||||
} = require('electron')
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config")
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, 'config.json')
|
||||
const config = require(configPath);
|
||||
|
||||
var chatDB = path.join(configFolder, 'chatDB.json')
|
||||
|
||||
// set date format
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', {
|
||||
timeStyle: 'long',
|
||||
dateStyle: 'full'
|
||||
});
|
||||
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
|
||||
|
||||
var chatDB = path.join(configFolder, 'chatDB')
|
||||
|
||||
// ---- MessageDB
|
||||
var PouchDB = require('pouchdb');
|
||||
var db = new PouchDB(chatDB);
|
||||
|
||||
|
||||
|
||||
// get all messages from database
|
||||
//var messages = db.get("messages").value()
|
||||
|
||||
// get all dxcallsigns in database
|
||||
|
||||
|
||||
|
||||
var dxcallsigns = new Set();
|
||||
|
||||
db.allDocs({
|
||||
include_docs: true,
|
||||
attachments: true
|
||||
}).then(function (result) {
|
||||
// handle result
|
||||
// get all dxcallsigns and append to list
|
||||
result.rows.forEach(function(item) {
|
||||
update_chat(item.doc)
|
||||
});
|
||||
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// SEND MSG
|
||||
document.getElementById("sendMessage").addEventListener("click", () => {
|
||||
dxcallsign = document.getElementById('chatModuleDxCall').value
|
||||
message = document.getElementById('chatModuleMessage').value
|
||||
|
||||
var dxcallsign = document.getElementById('chatModuleDxCall').value
|
||||
dxcallsign = dxcallsign.toUpperCase()
|
||||
message = document.getElementById('chatModuleMessage').value
|
||||
console.log(dxcallsign)
|
||||
let Data = {
|
||||
command: "sendMessage",
|
||||
dxcallsign : dxcallsign.toUpperCase(),
|
||||
mode : 10,
|
||||
command: "send_message",
|
||||
dxcallsign : dxcallsign,
|
||||
mode : 255,
|
||||
frames : 1,
|
||||
data : message,
|
||||
checksum : '123'
|
||||
};
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
|
||||
|
||||
var uuid = uuidv4();
|
||||
db.post({
|
||||
_id: uuid,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
dxcallsign: dxcallsign,
|
||||
dxgrid: 'NULL',
|
||||
msg: message,
|
||||
checksum: 'NULL',
|
||||
type: "transmit"
|
||||
}).then(function (response) {
|
||||
// handle response
|
||||
console.log("new database entry")
|
||||
console.log(response)
|
||||
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.get(uuid).then(function (doc) {
|
||||
// handle doc
|
||||
update_chat(doc)
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
// scroll to bottom
|
||||
var element = document.getElementById("message-container");
|
||||
element.scrollTo(0,element.scrollHeight);
|
||||
|
||||
// clear input
|
||||
document.getElementById('chatModuleMessage').value = ''
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
ipcRenderer.on('action-update-rx-msg-buffer', (event, arg) => {
|
||||
|
||||
var data = arg.data
|
||||
|
||||
|
||||
});
|
||||
|
||||
ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||
console.log(arg.data)
|
||||
var tbl = document.getElementById("rx-msg-data");
|
||||
document.getElementById("rx-msg-data").innerHTML = ''
|
||||
|
||||
var new_msg = arg.data
|
||||
new_msg.forEach(function(item) {
|
||||
console.log(item)
|
||||
//for (i = 0; i < arg.data.length; i++) {
|
||||
|
||||
let obj = new Object();
|
||||
|
||||
var encoded_data = atob(item.data);
|
||||
var splitted_data = encoded_data.split(split_char)
|
||||
|
||||
//obj.uuid = item.uuid;
|
||||
item.checksum = splitted_data[2]
|
||||
item.msg = splitted_data[3]
|
||||
//obj.dxcallsign = item.dxcallsign;
|
||||
//obj.dxgrid = item.dxgrid;
|
||||
//obj.timestamp = item.timestamp;
|
||||
|
||||
|
||||
|
||||
|
||||
for (i = 0; i < arg.data.length; i++) {
|
||||
|
||||
|
||||
|
||||
// now we update the received files list
|
||||
// check if message not exists in database.
|
||||
// this might cause big cpu load of file is getting too big
|
||||
/*
|
||||
if(!JSON.stringify(db.get("messages")).includes(item.uuid)){
|
||||
console.log("new message: " + item);
|
||||
db.get("messages").push(item).save();
|
||||
}
|
||||
*/
|
||||
|
||||
var row = document.createElement("tr");
|
||||
//https://stackoverflow.com/q/51421470
|
||||
|
||||
//https://stackoverflow.com/a/847196
|
||||
timestampRaw = arg.data[i]['TIMESTAMP']
|
||||
var date = new Date(timestampRaw * 1000);
|
||||
var hours = date.getHours();
|
||||
var minutes = "0" + date.getMinutes();
|
||||
var seconds = "0" + date.getSeconds();
|
||||
var datetime = hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
|
||||
db.put({
|
||||
_id: item.uuid,
|
||||
timestamp: item.timestamp,
|
||||
dxcallsign: item.dxcallsign,
|
||||
dxgrid: item.dxgrid,
|
||||
msg: item.msg,
|
||||
checksum: item.checksum,
|
||||
type: "received"
|
||||
}).then(function (response) {
|
||||
// handle response
|
||||
console.log("new database entry")
|
||||
console.log(response)
|
||||
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement('span');
|
||||
timestampText.innerText = datetime
|
||||
timestamp.appendChild(timestampText);
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
var dxCall = document.createElement("td");
|
||||
var dxCallText = document.createElement('span');
|
||||
dxCallText.innerText = arg.data[i]['DXCALLSIGN']
|
||||
dxCall.appendChild(dxCallText);
|
||||
|
||||
var message = document.createElement("td");
|
||||
var messageText = document.createElement('span');
|
||||
var messageString = arg.data[i]['RXDATA'][0]['d'] //data
|
||||
console.log(messageString)
|
||||
messageText.innerText = messageString
|
||||
message.appendChild(messageText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(dxCall);
|
||||
row.appendChild(message);
|
||||
|
||||
tbl.appendChild(row);
|
||||
db.get(item.uuid).then(function (doc) {
|
||||
// handle doc
|
||||
|
||||
// timestamp
|
||||
update_chat(doc)
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
console.log("...................................")
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Update chat list
|
||||
update_chat = function(obj) {
|
||||
|
||||
var dxcallsign = obj.dxcallsign;
|
||||
|
||||
// CALLSIGN LIST
|
||||
if(!(document.getElementById('chat-' + dxcallsign + '-list'))){
|
||||
var new_callsign = `
|
||||
<a class="list-group-item list-group-item-action" id="chat-${dxcallsign}-list" data-bs-toggle="list" href="#chat-${dxcallsign}" role="tab" aria-controls="chat-${dxcallsign}">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">${dxcallsign}</h5>
|
||||
<!--<small>3 days ago</small>-->
|
||||
</div>
|
||||
<!--<p class="mb-1">JN48ea</p>-->
|
||||
</a>
|
||||
|
||||
`;
|
||||
document.getElementById('list-tab').insertAdjacentHTML("beforeend", new_callsign);
|
||||
|
||||
var message_area = `
|
||||
<div class="tab-pane fade" id="chat-${dxcallsign}" role="tabpanel" aria-labelledby="chat-${dxcallsign}-list"></div>
|
||||
`;
|
||||
document.getElementById('nav-tabContent').insertAdjacentHTML("beforeend", message_area);
|
||||
|
||||
// create eventlistener for listening on clicking on a callsign
|
||||
document.getElementById('chat-' + dxcallsign + '-list').addEventListener('click', function() {
|
||||
document.getElementById('chatModuleDxCall').value = dxcallsign;
|
||||
|
||||
// scroll to bottom
|
||||
var element = document.getElementById("message-container");
|
||||
element.scrollTo(0,element.scrollHeight);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ipcRenderer.send('run-tnc-command', {"command" : "delRxMsgBuffer"});
|
||||
})
|
||||
|
||||
// APPEND MESSAGES TO CALLSIGN
|
||||
|
||||
var timestamp = dateFormat.format(obj.timestamp * 1000);
|
||||
|
||||
if(!(document.getElementById('msg-' + obj._id))){
|
||||
if (obj.type == 'received'){
|
||||
var new_message = `
|
||||
<div class="mt-3 mb-0 w-75" id="msg-${obj._id}">
|
||||
<p class="font-monospace text-small mb-0 text-muted text-break">${timestamp}</p>
|
||||
<div class="card border-light bg-light" id="msg-${obj._id}">
|
||||
<div class="card-body">
|
||||
<p class="card-text text-break text-wrap">${obj.msg}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (obj.type == 'transmit'){
|
||||
|
||||
|
||||
var new_message = `
|
||||
<div class="ml-auto mt-3 mb-0 w-75" style="margin-left: auto;">
|
||||
<p class="font-monospace text-right mb-0 text-muted" style="text-align: right;">${timestamp}</p>
|
||||
<div class="card text-right border-primary bg-primary" id="msg-${obj._id}">
|
||||
<div class="card-body">
|
||||
<p class="card-text text-white text-break text-wrap">${obj.msg}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
var id = "chat-" + obj.dxcallsign
|
||||
document.getElementById(id).insertAdjacentHTML("beforeend", new_message);
|
||||
var element = document.getElementById("message-container");
|
||||
element.scrollTo(0,element.scrollHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
73
gui/preload-log.js
Normal file
73
gui/preload-log.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const path = require('path');
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config")
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, 'config.json')
|
||||
const config = require(configPath);
|
||||
|
||||
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// here we could add filter buttons, somewhen later..
|
||||
|
||||
})
|
||||
|
||||
|
||||
ipcRenderer.on('action-update-log', (event, arg) => {
|
||||
|
||||
var entry = arg.entry
|
||||
|
||||
// remove ANSI characters from string, caused by color logging
|
||||
// https://stackoverflow.com/a/29497680
|
||||
entry = entry.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,'')
|
||||
|
||||
|
||||
var tbl = document.getElementById("log");
|
||||
var row = document.createElement("tr");
|
||||
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement('span');
|
||||
datetime = new Date();
|
||||
timestampText.innerText = datetime.toISOString();
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var logEntry = document.createElement("td");
|
||||
var logEntryText = document.createElement('span');
|
||||
logEntryText.innerText = entry
|
||||
logEntry.appendChild(logEntryText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(logEntry);
|
||||
tbl.appendChild(row);
|
||||
|
||||
|
||||
|
||||
if (logEntryText.innerText.includes('ALSA lib pcm')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[info ]')) {
|
||||
row.classList.add("table-info");
|
||||
}
|
||||
if (logEntryText.innerText.includes('[debug ]')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[warning ]')) {
|
||||
row.classList.add("table-warning");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[error ]')) {
|
||||
row.classList.add("table-danger");
|
||||
}
|
||||
|
||||
|
||||
// scroll to bottom of page
|
||||
// https://stackoverflow.com/a/11715670
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
|
||||
|
||||
})
|
1261
gui/preload-main.js
1261
gui/preload-main.js
File diff suppressed because it is too large
Load diff
363
gui/sock.js
363
gui/sock.js
|
@ -4,6 +4,9 @@ const {
|
|||
ipcRenderer
|
||||
} = require('electron')
|
||||
|
||||
const log = require('electron-log');
|
||||
const socketLog = log.scope('tnc');
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config")
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
|
@ -11,7 +14,10 @@ var configPath = path.join(configFolder, 'config.json')
|
|||
const config = require(configPath);
|
||||
|
||||
var client = new net.Socket();
|
||||
var msg = ''; // Current message, per connection.
|
||||
var socketchunk = ''; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
|
||||
// globals for getting new data only if available so we are saving bandwith
|
||||
var rxBufferLengthTnc = 0
|
||||
|
@ -20,14 +26,14 @@ var rxMsgBufferLengthTnc = 0
|
|||
var rxMsgBufferLengthGui = 0
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectTNC, 3000)
|
||||
setTimeout(connectTNC, 2000)
|
||||
|
||||
function connectTNC() {
|
||||
//exports.connectTNC = function(){
|
||||
//console.log('connecting to TNC...')
|
||||
//socketLog.info('connecting to TNC...')
|
||||
|
||||
//clear message buffer after reconnecting or inital connection
|
||||
msg = '';
|
||||
socketchunk = '';
|
||||
|
||||
if (config.tnclocation == 'localhost') {
|
||||
client.connect(3000, '127.0.0.1')
|
||||
|
@ -37,13 +43,29 @@ function connectTNC() {
|
|||
}
|
||||
|
||||
client.on('connect', function(data) {
|
||||
console.log('TNC connection established')
|
||||
socketLog.info('TNC connection established')
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwith: "-",
|
||||
rms_level: 0
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
|
||||
// also update tnc connection state
|
||||
ipcRenderer.send('request-update-tnc-connection', {tnc_connection : client.readyState});
|
||||
|
||||
|
||||
})
|
||||
|
||||
client.on('error', function(data) {
|
||||
console.log('TNC connection error');
|
||||
|
||||
socketLog.error('TNC connection error');
|
||||
socketLog.error(data);
|
||||
let Data = {
|
||||
tnc_connection: client.readyState,
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
|
@ -54,136 +76,236 @@ client.on('error', function(data) {
|
|||
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
|
||||
setTimeout(connectTNC, 2000)
|
||||
ipcRenderer.send('request-update-tnc-connection', {tnc_connection : client.readyState});
|
||||
client.destroy();
|
||||
setTimeout(connectTNC, 500)
|
||||
// setTimeout( function() { exports.connectTNC(tnc_host, tnc_port); }, 2000 );
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' TNC connection closed');
|
||||
socketLog.info(' TNC connection closed');
|
||||
setTimeout(connectTNC, 2000)
|
||||
});
|
||||
*/
|
||||
|
||||
client.on('end', function(data) {
|
||||
console.log('TNC connection ended');
|
||||
//setTimeout(connectTNC, 2000)
|
||||
setTimeout(connectTNC, 0)
|
||||
socketLog.info('TNC connection ended');
|
||||
ipcRenderer.send('request-update-tnc-connection', {tnc_connection : client.readyState});
|
||||
client.destroy();
|
||||
|
||||
// setTimeout( function() { exports.connectTNC(tnc_host, tnc_port); }, 2000 );
|
||||
setTimeout(connectTNC, 500)
|
||||
|
||||
});
|
||||
|
||||
//exports.writeTncCommand = function(command){
|
||||
writeTncCommand = function(command) {
|
||||
|
||||
//console.log(command)
|
||||
//socketLog.info(command)
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket openend, we are able to run commands
|
||||
|
||||
|
||||
|
||||
if (client.readyState == 'open') {
|
||||
//uiMain.setTNCconnection('open')
|
||||
client.write(command + '\n');
|
||||
|
||||
}
|
||||
|
||||
if (client.readyState == 'closed') {
|
||||
//uiMain.setTNCconnection('closed')
|
||||
//console.log("CLOSED!!!!!")
|
||||
socketLog.info("CLOSED!")
|
||||
|
||||
}
|
||||
|
||||
if (client.readyState == 'opening') {
|
||||
//uiMain.setTNCconnection('opening')
|
||||
//console.log("OPENING!!!!!")
|
||||
console.log('connecting to TNC...')
|
||||
socketLog.info('connecting to TNC...')
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
client.on('data', function(data) {
|
||||
client.on('data', function(socketdata) {
|
||||
|
||||
ipcRenderer.send('request-update-tnc-connection', {tnc_connection : client.readyState});
|
||||
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
data = data.toString('utf8'); // convert data to string
|
||||
msg += data.toString('utf8'); // append data to buffer so we can stick long data together
|
||||
//console.log(data)
|
||||
// check if we reached an EOF, if true, clear buffer and parse JSON data
|
||||
if (data.endsWith('"EOF":"EOF"}')) {
|
||||
//console.log(msg)
|
||||
try {
|
||||
//console.log(msg)
|
||||
data = JSON.parse(msg)
|
||||
} catch (e) {
|
||||
console.log(e); /* "SyntaxError*/
|
||||
|
||||
socketdata = socketdata.toString('utf8'); // convert data to string
|
||||
socketchunk += socketdata// append data to buffer so we can stick long data together
|
||||
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
|
||||
var data = ''
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
data = JSON.parse(socketchunk[0])
|
||||
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === ''){
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
msg = '';
|
||||
/* console.log("EOF detected!") */
|
||||
|
||||
//console.log(data)
|
||||
|
||||
if (data['COMMAND'] == 'TNC_STATE') {
|
||||
//console.log(data)
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
|
||||
//check if data is not empty
|
||||
if(socketchunk[i].length > 0){
|
||||
|
||||
//try to parse JSON
|
||||
try {
|
||||
|
||||
data = JSON.parse(socketchunk[i])
|
||||
|
||||
} catch (e) {
|
||||
socketLog.info(e); // "SyntaxError
|
||||
socketLog.info(socketchunk[i])
|
||||
socketchunk = ''
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (data['command'] == 'tnc_state') {
|
||||
//socketLog.info(data)
|
||||
// set length of RX Buffer to global variable
|
||||
rxBufferLengthTnc = data['RX_BUFFER_LENGTH']
|
||||
rxMsgBufferLengthTnc = data['RX_MSG_BUFFER_LENGTH']
|
||||
rxBufferLengthTnc = data['rx_buffer_length']
|
||||
rxMsgBufferLengthTnc = data['rx_msg_buffer_length']
|
||||
|
||||
let Data = {
|
||||
toe: Date.now() - data['TIMESTAMP'], // time of execution
|
||||
ptt_state: data['PTT_STATE'],
|
||||
busy_state: data['TNC_STATE'],
|
||||
arq_state: data['ARQ_STATE'],
|
||||
ptt_state: data['ptt_state'],
|
||||
busy_state: data['tnc_state'],
|
||||
arq_state: data['arq_state'],
|
||||
arq_session: data['arq_session'],
|
||||
//channel_state: data['CHANNEL_STATE'],
|
||||
frequency: data['FREQUENCY'],
|
||||
mode: data['MODE'],
|
||||
bandwith: data['BANDWITH'],
|
||||
rms_level: (data['AUDIO_RMS'] / 1000) * 100,
|
||||
fft: data['FFT'],
|
||||
scatter: data['SCATTER'],
|
||||
info: data['INFO'],
|
||||
rx_buffer_length: data['RX_BUFFER_LENGTH'],
|
||||
rx_msg_buffer_length: data['RX_MSG_BUFFER_LENGTH'],
|
||||
tx_n_max_retries: data['TX_N_MAX_RETRIES'],
|
||||
arq_tx_n_frames_per_burst: data['ARQ_TX_N_FRAMES_PER_BURST'],
|
||||
arq_tx_n_bursts: data['ARQ_TX_N_BURSTS'],
|
||||
arq_tx_n_current_arq_frame: data['ARQ_TX_N_CURRENT_ARQ_FRAME'],
|
||||
arq_tx_n_total_arq_frames: data['ARQ_TX_N_TOTAL_ARQ_FRAMES'],
|
||||
arq_rx_frame_n_bursts: data['ARQ_RX_FRAME_N_BURSTS'],
|
||||
arq_rx_n_current_arq_frame: data['ARQ_RX_N_CURRENT_ARQ_FRAME'],
|
||||
arq_n_arq_frames_per_data_frame: data['ARQ_N_ARQ_FRAMES_PER_DATA_FRAME'],
|
||||
arq_bytes_per_minute: data['ARQ_BYTES_PER_MINUTE'],
|
||||
arq_compression_factor: data['ARQ_COMPRESSION_FACTOR'],
|
||||
total_bytes: data['TOTAL_BYTES'],
|
||||
arq_transmission_percent: data['ARQ_TRANSMISSION_PERCENT'],
|
||||
stations: data['STATIONS'],
|
||||
beacon_state: data['BEACON_STATE'],
|
||||
frequency: data['frequency'],
|
||||
speed_level: data['speed_level'],
|
||||
mode: data['mode'],
|
||||
bandwith: data['bandwith'],
|
||||
rms_level: (data['audio_rms'] / 1000) * 100,
|
||||
fft: data['fft'],
|
||||
channel_busy: data['channel_busy'],
|
||||
scatter: data['scatter'],
|
||||
info: data['info'],
|
||||
rx_buffer_length: data['rx_buffer_length'],
|
||||
rx_msg_buffer_length: data['rx_msg_buffer_length'],
|
||||
tx_n_max_retries: data['tx_n_max_retries'],
|
||||
arq_tx_n_frames_per_burst: data['arq_tx_n_frames_per_burst'],
|
||||
arq_tx_n_bursts: data['arq_tx_n_bursts'],
|
||||
arq_tx_n_current_arq_frame: data['arq_tx_n_current_arq_frame'],
|
||||
arq_tx_n_total_arq_frames: data['arq_tx_n_total_arq_frames'],
|
||||
arq_rx_frame_n_bursts: data['arq_rx_frame_n_bursts'],
|
||||
arq_rx_n_current_arq_frame: data['arq_rx_n_current_arq_frame'],
|
||||
arq_n_arq_frames_per_data_frame: data['arq_n_arq_frames_per_data_frame'],
|
||||
arq_bytes_per_minute: data['arq_bytes_per_minute'],
|
||||
arq_compression_factor: data['arq_compression_factor'],
|
||||
total_bytes: data['total_bytes'],
|
||||
arq_transmission_percent: data['arq_transmission_percent'],
|
||||
stations: data['stations'],
|
||||
beacon_state: data['beacon_state'],
|
||||
};
|
||||
//console.log(Data)
|
||||
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
}
|
||||
|
||||
if (data['COMMAND'] == 'RX_BUFFER') {
|
||||
/* A TEST WITH STREAMING DATA .... */
|
||||
// if we received data through network stream, we get a single data item
|
||||
if (data['arq'] == 'received') {
|
||||
dataArray = []
|
||||
messageArray = []
|
||||
|
||||
rxBufferLengthGui = data['DATA-ARRAY'].length
|
||||
let Data = {
|
||||
data: data['DATA-ARRAY'],
|
||||
socketLog.info(data)
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
var encoded_data = atob(data['data'])
|
||||
var splitted_data = encoded_data.split(split_char)
|
||||
|
||||
|
||||
if(splitted_data[0] == 'f'){
|
||||
dataArray.push(data)
|
||||
}
|
||||
|
||||
if(splitted_data[0] == 'm'){
|
||||
messageArray.push(data)
|
||||
}
|
||||
|
||||
rxBufferLengthGui = dataArray.length
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send('request-update-rx-buffer', Files);
|
||||
|
||||
rxMsgBufferLengthGui = messageArray.length
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
|
||||
ipcRenderer.send('request-update-rx-buffer', Data);
|
||||
//ipcRenderer.send('request-update-rx-msg-buffer', Messages);
|
||||
ipcRenderer.send('request-new-msg-received', Messages);
|
||||
}
|
||||
|
||||
if (data['COMMAND'] == 'RX_MSG_BUFFER') {
|
||||
// if we manually checking for the rx buffer we are getting an array of multiple data
|
||||
if (data['command'] == 'rx_buffer') {
|
||||
socketLog.info(data)
|
||||
// iterate through buffer list and sort it to file or message array
|
||||
dataArray = []
|
||||
messageArray = []
|
||||
|
||||
rxMsgBufferLengthGui = data['DATA-ARRAY'].length
|
||||
let Data = {
|
||||
data: data['DATA-ARRAY'],
|
||||
for (i = 0; i < data['data-array'].length; i++) {
|
||||
try{
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
var encoded_data = atob(data['data-array'][i]['data'])
|
||||
var splitted_data = encoded_data.split(split_char)
|
||||
|
||||
|
||||
if(splitted_data[0] == 'f'){
|
||||
dataArray.push(data['data-array'][i])
|
||||
|
||||
}
|
||||
|
||||
if(splitted_data[0] == 'm'){
|
||||
messageArray.push(data['data-array'][i])
|
||||
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
socketLog.info(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
rxBufferLengthGui = dataArray.length
|
||||
let Files = {
|
||||
data: dataArray,
|
||||
};
|
||||
ipcRenderer.send('request-update-rx-buffer', Files);
|
||||
|
||||
rxMsgBufferLengthGui = messageArray.length
|
||||
let Messages = {
|
||||
data: messageArray,
|
||||
};
|
||||
//ipcRenderer.send('request-update-rx-msg-buffer', Messages);
|
||||
ipcRenderer.send('request-new-msg-received', Messages);
|
||||
|
||||
|
||||
ipcRenderer.send('request-update-rx-msg-buffer', Data);
|
||||
}
|
||||
// check if EOF ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = '';
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function hexToBytes(hex) {
|
||||
|
@ -195,57 +317,74 @@ function hexToBytes(hex) {
|
|||
|
||||
//Get TNC State
|
||||
exports.getTncState = function() {
|
||||
command = '{"type" : "GET", "command" : "TNC_STATE", "timestamp" : ' + Date.now() + '}';
|
||||
command = '{"type" : "get", "command" : "tnc_state"}';
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
//Get DATA State
|
||||
exports.getDataState = function() {
|
||||
command = '{"type" : "GET", "command" : "DATA_STATE", "timestamp" : ' + Date.now() + '}';
|
||||
command = '{"type" : "get", "command" : "data_state"}';
|
||||
//writeTncCommand(command)
|
||||
}
|
||||
|
||||
//Get Heard Stations
|
||||
//exports.getHeardStations = function() {
|
||||
// command = '{"type" : "GET", "command" : "HEARD_STATIONS", "timestamp" : ' + Date.now() + '}';
|
||||
// writeTncCommand(command)
|
||||
//}
|
||||
|
||||
// Send Ping
|
||||
exports.sendPing = function(dxcallsign) {
|
||||
command = '{"type" : "PING", "command" : "PING", "dxcallsign" : "' + dxcallsign + '", "timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "ping", "command" : "ping", "dxcallsign" : "' + dxcallsign + '"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// Send CQ
|
||||
exports.sendCQ = function() {
|
||||
command = '{"type" : "BROADCAST", "command" : "CQCQCQ", "timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "broadcast", "command" : "cqcqcq"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// Send File
|
||||
exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data, checksum) {
|
||||
command = '{"type" : "ARQ", "command" : "sendFile", "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "filename" : "' + filename + '", "filetype" : "' + filetype + '", "data" : "' + data + '", "checksum" : "' + checksum + '", "timestamp" : ' + Date.now() + '}'
|
||||
|
||||
socketLog.info(data)
|
||||
socketLog.info(filetype)
|
||||
socketLog.info(filename)
|
||||
|
||||
var datatype = "f"
|
||||
|
||||
data = datatype + split_char + filename + split_char + filetype + split_char + checksum + split_char + data
|
||||
socketLog.info(data)
|
||||
socketLog.info(btoa(data))
|
||||
data = btoa(data)
|
||||
|
||||
command = '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '"}]}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// Send Message
|
||||
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum) {
|
||||
command = '{"type" : "ARQ", "command" : "sendMessage", "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '", "timestamp" : ' + Date.now() + '}'
|
||||
console.log(command)
|
||||
socketLog.info(data)
|
||||
|
||||
var datatype = "m"
|
||||
data = datatype + split_char + split_char + checksum + split_char + data
|
||||
socketLog.info(data)
|
||||
socketLog.info(btoa(data))
|
||||
data = btoa(data)
|
||||
|
||||
//command = '{"type" : "arq", "command" : "send_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}'
|
||||
command = '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '"}]}'
|
||||
socketLog.info(command)
|
||||
socketLog.info("-------------------------------------")
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
|
||||
//STOP TRANSMISSION
|
||||
exports.stopTransmission = function() {
|
||||
command = '{"type" : "ARQ", "command": "stopTransmission", "timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "arq", "command": "stop_transmission"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// Get RX BUffer
|
||||
exports.getRxBuffer = function() {
|
||||
command = '{"type" : "GET", "command" : "RX_BUFFER", "timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "get", "command" : "rx_buffer"}'
|
||||
|
||||
// call command only if new data arrived
|
||||
if (rxBufferLengthGui != rxBufferLengthTnc) {
|
||||
|
@ -253,35 +392,27 @@ exports.getRxBuffer = function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Get RX MSG BUffer
|
||||
exports.getMsgRxBuffer = function() {
|
||||
command = '{"type" : "GET", "command" : "RX_MSG_BUFFER", "timestamp" : ' + Date.now() + '}'
|
||||
|
||||
// call command only if new data arrived
|
||||
if (rxMsgBufferLengthGui != rxMsgBufferLengthTnc) {
|
||||
writeTncCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE RX MSG BUffer
|
||||
exports.delRxMsgBuffer = function() {
|
||||
command = '{"type" : "SET", "command" : "DEL_RX_MSG_BUFFER", "timestamp" : ' + Date.now() + '}'
|
||||
|
||||
// call command only if new data arrived
|
||||
if (rxMsgBufferLengthGui != rxMsgBufferLengthTnc) {
|
||||
writeTncCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
// START BEACON
|
||||
exports.startBeacon = function(interval) {
|
||||
command = '{"type" : "BROADCAST", "command" : "START_BEACON", "parameter": "' + interval + '","timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "broadcast", "command" : "start_beacon", "parameter": "' + interval + '"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// STOP BEACON
|
||||
exports.stopBeacon = function() {
|
||||
command = '{"type" : "BROADCAST", "command" : "STOP_BEACON", "timestamp" : ' + Date.now() + '}'
|
||||
command = '{"type" : "broadcast", "command" : "stop_beacon"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// OPEN ARQ SESSION
|
||||
exports.connectARQ = function(dxcallsign) {
|
||||
command = '{"type" : "arq", "command" : "connect", "dxcallsign": "'+ dxcallsign + '"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// CLOSE ARQ SESSION
|
||||
exports.disconnectARQ = function() {
|
||||
command = '{"type" : "arq", "command" : "disconnect"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 308 KiB |
|
@ -7,7 +7,9 @@
|
|||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap-icons/font/bootstrap-icons.css">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - CHAT</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -18,34 +20,59 @@
|
|||
<script src="../node_modules/chart.js/dist/chart.min.js"></script>
|
||||
<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>
|
||||
|
||||
<div class="input-group m-1">
|
||||
<input class="form-control" maxlength="6" style="max-width: 6rem; text-transform:uppercase" id="chatModuleDxCall" placeholder="DX CALL"></input>
|
||||
<input class="form-control" id="chatModuleMessage" placeholder="Message"></input>
|
||||
<button class="btn btn-sm btn-primary me-2" id="sendMessage" type="button">SEND MSG</button>
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row h-100">
|
||||
<div class="col-4 p-0">
|
||||
<! ------Chats area ---------------------------------------------------------------------->
|
||||
|
||||
<div class="list-group rounded-0" id="list-tab" role="tablist">
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-8 border vh-100 ">
|
||||
<! ------messages area ---------------------------------------------------------------------->
|
||||
<div class="container overflow-auto" id="message-container" style="height: 90%">
|
||||
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
</div>
|
||||
|
||||
<!--<div class="container position-absolute bottom-0">-->
|
||||
|
||||
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<nav class="navbar fixed-bottom navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="input-group bottom-0 w-100">
|
||||
<input class="form-control" maxlength="8" style="max-width: 6rem; text-transform:uppercase" id="chatModuleDxCall" placeholder="DX CALL"></input>
|
||||
<input class="form-control" id="chatModuleMessage" placeholder="Message"></input>
|
||||
<button class="btn btn-sm btn-primary me-2" id="sendMessage" type="button"><i class="bi bi-send" style="font-size: 1.2rem; color: white;"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">DXCall</th>
|
||||
<!--<th scope="col">DXGrid</th>
|
||||
<th scope="col">Distance</th>-->
|
||||
<th scope="col">Message</th>
|
||||
<!--<th scope="col">SNR</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rx-msg-data">
|
||||
<!--
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Mark</td>
|
||||
<td>Otto</td>
|
||||
<td>@mdo</td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
BIN
gui/src/img/icon.png
Normal file
BIN
gui/src/img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 646 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
@ -8,89 +8,151 @@
|
|||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="script-src 'self';">-->
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" id="bootstrap_theme" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap-icons/font/bootstrap-icons.css">
|
||||
|
||||
|
||||
<!-- Waterfall CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="waterfall/waterfall.css" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA</title>
|
||||
<title>FreeDATA by DJ2LS</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- SECONDARY NAVBAR -->
|
||||
<nav class="navbar bg-light fixed-top navbar-underline mt-0 mb-1 pb-1 pt-1 shadow">
|
||||
<nav class="navbar bg-light fixed-top mt-0 mb-1 pb-1 pt-1 shadow-sm">
|
||||
<div class="container-fluid mt-0">
|
||||
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||
<div class="btn-group btn-group-sm me-2" role="group" aria-label="local-remote-switch toggle button group" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="true" title="Select a local or a remote location of your tnc daemon. Normally local is the preferred option.">
|
||||
<input type="radio" class="btn-check" name="local-remote-switch" id="local-remote-switch1" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-secondary" for="local-remote-switch1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-pc-display-horizontal" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.5 0A1.5 1.5 0 0 0 0 1.5v7A1.5 1.5 0 0 0 1.5 10H6v1H1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-5v-1h4.5A1.5 1.5 0 0 0 16 8.5v-7A1.5 1.5 0 0 0 14.5 0h-13Zm0 1a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.5-.5h-13ZM12 12.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm2 0a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0ZM1.5 12a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5ZM1 14.25a.25.25 0 0 1 .25-.25h5.5a.25.25 0 1 1 0 .5h-5.5a.25.25 0 0 1-.25-.25Z" />
|
||||
</svg>
|
||||
<label class="btn btn-sm btn-outline-secondary" for="local-remote-switch1">
|
||||
<i class="bi bi-pc-display-horizontal" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="local-remote-switch" id="local-remote-switch2" autocomplete="off">
|
||||
<label class="btn btn-outline-secondary" for="local-remote-switch2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-ethernet" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2ZM1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2Zm13 11.5v-7a.5.5 0 0 0-.5-.5H12V4.5a.5.5 0 0 0-.5-.5h-1v-.5A.5.5 0 0 0 10 3H6a.5.5 0 0 0-.5.5V4h-1a.5.5 0 0 0-.5.5V6H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5ZM3.75 11a.25.25 0 0 0-.25.25v1.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-1.5a.25.25 0 0 0-.25-.25h-.5Zm2 0a.25.25 0 0 0-.25.25v1.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-1.5a.25.25 0 0 0-.25-.25h-.5Zm1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5ZM9.75 11a.25.25 0 0 0-.25.25v1.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-1.5a.25.25 0 0 0-.25-.25h-.5Zm1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5Z" />
|
||||
</svg>
|
||||
<label class="btn btn-sm btn-outline-secondary" for="local-remote-switch2">
|
||||
<i class="bi bi-ethernet" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm me-2" id="remote-tnc-field"> <span class="input-group-text" id="basic-addon1">IP</span>
|
||||
<div class="input-group input-group-sm me-2" id="remote-tnc-field"> <span class="input-group-text" id="basic-addon1">TNC IP</span>
|
||||
<input type="text" class="form-control" placeholder="ip adress" id="tnc_adress" value="192.168.178.163" maxlength="17" style="width: 8rem" aria-label="Username" aria-describedby="basic-addon1"> <span class="input-group-text" id="basic-addon1">:</span>
|
||||
<input type="text" class="form-control" placeholder="port" value="3000" id="tnc_port" maxlength="5" max="65534" min="1025" style="width: 4rem" aria-label="Username" aria-describedby="basic-addon1">
|
||||
<button class="btn btn-sm btn-danger" id="daemon_connection_state" type="button" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-diagram-3" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5v-1zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z" />
|
||||
</svg>
|
||||
<i class="bi bi-diagram-3" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="true" title="Start or stop the tnc process. Please set your audio and radio settings first!">
|
||||
<button type="button" id="startTNC" class="btn btn-success">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-play-fill" viewBox="0 0 16 16">
|
||||
<path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z" />
|
||||
</svg>
|
||||
<button type="button" id="startTNC" class="btn btn-sm btn-success">
|
||||
<i class="bi bi-play-fill" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
<!-- TNC LOG BUTTON -->
|
||||
<button class="btn btn-sm btn-secondary" id="tncLog" type="button">
|
||||
<i class="bi bi-activity" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
<div class="input-group-text">TNC</div>
|
||||
<!-- <span class="input-group-text" id="tnc_running_state" style="width: 5rem">---</span>-->
|
||||
<button type="button" data-bs-toggle="collapse" data-bs-target=".multi-collapse" id="stopTNC" class="btn btn-sm btn-danger">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-x-octagon-fill" viewBox="0 0 16 16">
|
||||
<path d="M11.46.146A.5.5 0 0 0 11.107 0H4.893a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146zm-6.106 4.5L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z" />
|
||||
</svg>
|
||||
<i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openRFChat" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Open the HF chat module. This is currently just a test and not finished, yet!"> <strong>RF Chat</strong>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-chat-left-text-fill" viewBox="0 0 16 16">
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4.414a1 1 0 0 0-.707.293L.854 15.146A.5.5 0 0 1 0 14.793V2zm3.5 1a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1h-9zm0 2.5a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1h-9zm0 2.5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5z" />
|
||||
</svg>
|
||||
</button> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View the received files. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-4 position-relative" id="openRFChat" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Open the HF chat module. This is currently just a test and not finished, yet!" disabled> <strong>RF Chat</strong>
|
||||
<i class="bi bi-chat-left-text-fill" style="font-size: 1rem; color: white;"></i>
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">soon...</span>
|
||||
</button>
|
||||
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View the received files. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#receivedFilesSidebar" id="openReceivedFiles" type="button"> <strong>Received Files </strong>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-file-earmark-arrow-down-fill" viewBox="0 0 16 16">
|
||||
<path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0zM9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1zm-1 4v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 11.293V7.5a.5.5 0 0 1 1 0z" />
|
||||
</svg>
|
||||
<i class="bi bi-file-earmark-arrow-down-fill" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</span>
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Send files through HF. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openDataModule" data-bs-toggle="offcanvas" data-bs-target="#transmitFileSidebar" type="button" > <strong>Transmit File </strong>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-file-earmark-arrow-up-fill" viewBox="0 0 16 16">
|
||||
<path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0zM9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1zM6.354 9.854a.5.5 0 0 1-.708-.708l2-2a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 8.707V12.5a.5.5 0 0 1-1 0V8.707L6.354 9.854z" />
|
||||
</svg>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</span>
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="true" title="Settings and Info">
|
||||
<button type="button" id="infoModalButton" data-bs-toggle="modal" data-bs-target="#infoModal" class="btn btn-sm btn-secondary">
|
||||
<i class="bi bi-sliders" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="blurdiv" style="-webkit-Filter: blur(10px)">
|
||||
<div id="blurdiv" style="-webkit-Filter: blur(0px)">
|
||||
<!--beginn of blur div -->
|
||||
<!-------------------------------- MAIN AREA ---------------->
|
||||
<!-------------------------------- UPDATE TOASTS ---------------->
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative" style="z-index: 500">
|
||||
<div class="toast-container position-absolute top-0 start-0 end-0 p-3">
|
||||
<!-- CHECKING FOR UPDATE -->
|
||||
<div class="toast align-items-center text-white bg-secondary border-0" id="toastUpdateChecking" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body"><i class="bi bi-search" style="font-size: 1rem; color: white;"></i> Checking for update</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DOWNLOAD PROGRESS -->
|
||||
<div class="toast align-items-center bg-white" id="toastUpdateProgress" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="false" data-bs-autohide="false">
|
||||
<!--<div class="d-flex"> -->
|
||||
<div class="toast-header"> <strong class="me-auto">Downloading...</strong>
|
||||
<small><span id="toastUpdateProgressSpeed"></span></small>
|
||||
</div>
|
||||
<div class="toast-body w-100">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: 75%" role="progressbar" id="toastUpdateProgressBar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"> <span id="toastUpdateProgressInfo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DOWNLOADED -->
|
||||
<div class="toast bg-white" role="alert" data-bs-autohide="false" id="toastUpdateDownloaded" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header"> <strong class="me-auto"> Update ready...</strong>
|
||||
</div>
|
||||
<div class="toast-body">App is going to restart automatically...</div>
|
||||
</div>
|
||||
<!-- UPDATE AVAILABLE -->
|
||||
|
||||
<div class="toast align-items-center text-white bg-primary border-0" data-bs-autohide="false" id="toastUpdateAvailable" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body"> <i class="bi bi-activity" style="font-size: 1rem; color: white;"></i> Preparing for download...</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- UPDATE NOT AVAILABLE -->
|
||||
<div class="toast align-items-center text-white bg-success border-0" id="toastUpdateNotAvailable" role="alert" aria-live="assertive" aria-atomic="true"data-bs-delay="1500">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body"> <i class="bi bi-activity" style="font-size: 1rem; color: white;"></i> Up to date!</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NOT CHECKING FOR UPDATE -->
|
||||
<div class="toast align-items-center text-white bg-warning border-0" id="toastUpdateNotChecking" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">Update not available - Please try again later!</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-------------------------------- INFO TOASTS ---------------->
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative" style="z-index: 500">
|
||||
<div class="toast-container position-absolute top-0 end-0 p-3">
|
||||
<!-- SENDING CQ -->
|
||||
<div class="toast align-items-center text-white bg-primary border-0" id="toastCQsending" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">Sending CQ CQ CQ</div>
|
||||
<div class="toast-body"><i class="bi bi-activity" style="font-size: 1rem; color: white;"></i>Sending CQ CQ CQ</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- RECEIVING CQ -->
|
||||
|
@ -213,29 +275,18 @@
|
|||
<div class="col">
|
||||
<div class="card text-dark mb-0">
|
||||
<div class="card-header p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor" class="bi bi-volume-up" viewBox="0 0 16 16">
|
||||
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" />
|
||||
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" />
|
||||
<path d="M10.025 8a4.486 4.486 0 0 1-1.318 3.182L8 10.475A3.489 3.489 0 0 0 9.025 8c0-.966-.392-1.841-1.025-2.475l.707-.707A4.486 4.486 0 0 1 10.025 8zM7 4a.5.5 0 0 0-.812-.39L3.825 5.5H1.5A.5.5 0 0 0 1 6v4a.5.5 0 0 0 .5.5h2.325l2.363 1.89A.5.5 0 0 0 7 12V4zM4.312 6.39 6 5.04v5.92L4.312 9.61A.5.5 0 0 0 4 9.5H2v-3h2a.5.5 0 0 0 .312-.11z" />
|
||||
</svg> <strong>AUDIO SETTINGS</strong>
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i><strong>AUDIO</strong>
|
||||
</div>
|
||||
<div class="card-body p-2 mb-1">
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-mic-fill" viewBox="0 0 16 16">
|
||||
<path d="M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z"/>
|
||||
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
|
||||
</svg>
|
||||
<i class="bi bi-mic-fill" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<select class="form-select form-select-sm" id="audio_input_selectbox" aria-label=".form-select-sm">
|
||||
<!-- <option selected value="3011">USB Interface</option>-->
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-volume-up-fill" viewBox="0 0 16 16">
|
||||
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"/>
|
||||
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"/>
|
||||
<path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06z"/>
|
||||
</svg>
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<select class="form-select form-select-sm" id="audio_output_selectbox" aria-label=".form-select-sm"></select>
|
||||
</div>
|
||||
|
@ -246,26 +297,29 @@
|
|||
<div class="col">
|
||||
<div class="card text-dark mb-0">
|
||||
<div class="card-header p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor" class="bi bi-projector" viewBox="0 0 16 16">
|
||||
<path d="M14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM2.5 6a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Zm0 2a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Z" />
|
||||
<path fill-rule="evenodd" d="M0 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2 1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1H5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1 2 2 0 0 1-2-2V6Zm2-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z" />
|
||||
</svg> <strong>RADIO SETTINGS</strong>
|
||||
<i class="bi bi-projector" style="font-size: 1rem; color: black;"></i><strong>RADIO</strong>
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="waterfall-scatter-switch toggle button group">
|
||||
<input type="radio" class="btn-check" name="radio-control-switch" id="radio-control-switch0" autocomplete="off" checked>
|
||||
<label class="btn btn-sm btn-outline-secondary" for="radio-control-switch0">
|
||||
<i class="bi bi-x-circle" style="font-size: 0.8rem; color: black;"></i>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="radio-control-switch" id="radio-control-switch1" autocomplete="off" checked>
|
||||
<label class="btn btn-sm btn-outline-secondary" for="radio-control-switch1"><strong>direct</strong>
|
||||
</label>
|
||||
<!--
|
||||
<input type="radio" class="btn-check" name="radio-control-switch" id="radio-control-switch2" autocomplete="off">
|
||||
|
||||
<label class="btn btn-sm btn-outline-secondary" for="radio-control-switch2"><strong>rigctl</strong>
|
||||
</label>
|
||||
-->
|
||||
<input type="radio" class="btn-check" name="radio-control-switch" id="radio-control-switch3" autocomplete="off">
|
||||
|
||||
<label class="btn btn-sm btn-outline-secondary" for="radio-control-switch3"><strong>rigctld</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group" role="group" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="true" title="Set advanced hamlib settings like stop_bits and data_bits or a different port for a ptt device">
|
||||
<button type="button" id="advancedHamlibSettingsButton" data-bs-toggle="modal" data-bs-target="#advancedHamlibSettingsModal" class="btn btn-sm btn-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sliders" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z" />
|
||||
</svg>
|
||||
<i class="bi bi-sliders" style="font-size: 0.8rem; color: black;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
|
@ -275,11 +329,9 @@
|
|||
<div class="card-body p-2">
|
||||
<div id="radio-control-direct">
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-projector-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 4a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2 1 1 0 0 0 1 1h1a1 1 0 0 0 1-1h6a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1 2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2Zm.5 2h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1 0-1ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm-12 1a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Z"/>
|
||||
</svg>
|
||||
<i class="bi bi-projector-fill" style="font-size: 0.8rem; color: black;"></i>
|
||||
</span>
|
||||
<!--<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_deviceid" style="width:7rem">-->
|
||||
|
||||
<input class="form-control" list="datalistOptions" id="hamlib_deviceid" placeholder="Search radio..." style="width:7rem">
|
||||
<datalist id="datalistOptions">
|
||||
<option value="RIG_MODEL_ADT_200A">ADAT www.adat.ch ADT-200A</option>
|
||||
|
@ -546,12 +598,11 @@
|
|||
<option value="RIG_MODEL_FTDX101MP">Yaesu FTDX-101MP</option>
|
||||
</datalist> <span class="input-group-text" id="basic-addon1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
|
||||
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
|
||||
</svg>
|
||||
<i class="bi bi-alt" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_ptt_protocol" style="width: 0.5rem">
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="RTS">Serial RTS</option>
|
||||
|
@ -565,9 +616,7 @@
|
|||
<!--<hr class="m-1">-->
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-usb-symbol" viewBox="0 0 16 16">
|
||||
<path d="m7.792.312-1.533 2.3A.25.25 0 0 0 6.467 3H7.5v7.319a2.5 2.5 0 0 0-.515-.298L5.909 9.56A1.5 1.5 0 0 1 5 8.18v-.266a1.5 1.5 0 1 0-1 0v.266a2.5 2.5 0 0 0 1.515 2.298l1.076.461a1.5 1.5 0 0 1 .888 1.129 2.001 2.001 0 1 0 1.021-.006v-.902a1.5 1.5 0 0 1 .756-1.303l1.484-.848A2.5 2.5 0 0 0 11.995 7h.755a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25v2.5c0 .138.112.25.25.25h.741a1.5 1.5 0 0 1-.747 1.142L8.76 8.99a2.584 2.584 0 0 0-.26.17V3h1.033a.25.25 0 0 0 .208-.389L8.208.312a.25.25 0 0 0-.416 0Z"/>
|
||||
</svg>
|
||||
<i class="bi bi-usb-symbol" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_deviceport" style="width:7rem">
|
||||
|
@ -588,24 +637,22 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div id="radio-control-rigctl">
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-projector-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 4a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2 1 1 0 0 0 1 1h1a1 1 0 0 0 1-1h6a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1 2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2Zm.5 2h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1 0-1ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm-12 1a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Z"/>
|
||||
</svg>
|
||||
<i class="bi bi-projector" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" placeholder="Device id" id="hamlib_deviceid_rigctl" aria-label="Device ID" aria-describedby="basic-addon1"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
|
||||
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
|
||||
</svg>
|
||||
|
||||
<i class="bi bi-alt" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_ptt_protocol_rigctl" style="width: 0.5rem">
|
||||
<option value="RIG">RIG</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="RTS">Serial RTS</option>
|
||||
<option value="DTR-H">Serial DTR-High</option>
|
||||
<option value="DTR-H">Serial DTR-Low</option>
|
||||
<option value="DTR-L">Serial DTR-Low</option>
|
||||
<option value="PARALLEL">Rig PARALLEL</option>
|
||||
<option value="MICDATA">Rig MICDATA</option>
|
||||
<option value="CM108">Rig CM108</option>
|
||||
|
@ -613,14 +660,11 @@
|
|||
</div>
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-usb-symbol" viewBox="0 0 16 16">
|
||||
<path d="m7.792.312-1.533 2.3A.25.25 0 0 0 6.467 3H7.5v7.319a2.5 2.5 0 0 0-.515-.298L5.909 9.56A1.5 1.5 0 0 1 5 8.18v-.266a1.5 1.5 0 1 0-1 0v.266a2.5 2.5 0 0 0 1.515 2.298l1.076.461a1.5 1.5 0 0 1 .888 1.129 2.001 2.001 0 1 0 1.021-.006v-.902a1.5 1.5 0 0 1 .756-1.303l1.484-.848A2.5 2.5 0 0 0 11.995 7h.755a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25v2.5c0 .138.112.25.25.25h.741a1.5 1.5 0 0 1-.747 1.142L8.76 8.99a2.584 2.584 0 0 0-.26.17V3h1.033a.25.25 0 0 0 .208-.389L8.208.312a.25.25 0 0 0-.416 0Z"/>
|
||||
</svg>
|
||||
<i class="bi bi-usb-symbol" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_deviceport_rigctl" style="width:7rem">
|
||||
<!--<option selected value="/dev/ttyUSB0">/dev/ttyUSB0</option>
|
||||
<option value="/dev/ttyUSB1">/dev/ttyUSB1</option>-->
|
||||
|
||||
</select> <span class="input-group-text" id="basic-addon1">Speed</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_serialspeed_rigctl">
|
||||
<option value="1200">1200</option>
|
||||
|
@ -636,11 +680,11 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div id="radio-control-rigctld">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<input type="text" class="form-control" placeholder="rigctld IP" id="hamlib_rigctld_ip" aria-label="Device IP" aria-describedby="basic-addon1">
|
||||
<input type="text" class="form-control" placeholder="rigctld port" id="hamlib_rigctld_port" aria-label="Device Port" aria-describedby="basic-addon1">
|
||||
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">IP</span>
|
||||
<input type="text" class="form-control" placeholder="rigctld IP" id="hamlib_rigctld_ip" aria-label="Device IP" aria-describedby="basic-addon1"> <span class="input-group-text" id="basic-addon1">:</span>
|
||||
<input type="text" class="form-control" placeholder="rigctld port" id="hamlib_rigctld_port" aria-label="Device Port" aria-describedby="basic-addon1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -657,31 +701,43 @@
|
|||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Enter your callsign and save it"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-bounding-box" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm8-9a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
</svg>
|
||||
|
||||
<i class="bi bi-person-bounding-box" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" style="max-width: 6rem; text-transform:uppercase" placeholder="callsign" pattern="[A-Z]*" id="myCall" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<button class="btn btn-success" id="saveMyCall" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-check2" viewBox="0 0 16 16">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</svg>
|
||||
<input type="text" class="form-control" style="width: 5rem; text-transform:uppercase" placeholder="callsign" pattern="[A-Z]*" id="myCall" maxlength="8" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="myCallSSID">
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-success" id="saveMyCall" type="button">
|
||||
<i class="bi bi-check2" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Enter your gridsquare and save it"> <span class="input-group-text" id="basic-addon1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-house-fill" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293l6-6zm5-.793V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"/>
|
||||
<path fill-rule="evenodd" d="M7.293 1.5a1 1 0 0 1 1.414 0l6.647 6.646a.5.5 0 0 1-.708.708L8 2.207 1.354 8.854a.5.5 0 1 1-.708-.708L7.293 1.5z"/>
|
||||
</svg>
|
||||
<i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i>
|
||||
|
||||
</span>
|
||||
<input type="text" class="form-control mr-1" style="max-width: 6rem" placeholder="locator" id="myGrid" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<button class="btn btn-success" id="saveMyGrid" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-check2" viewBox="0 0 16 16">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</svg>
|
||||
<button class="btn btn-sm btn-success" id="saveMyGrid" type="button">
|
||||
<i class="bi bi-check2" style="font-size: 1rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -693,17 +749,26 @@
|
|||
<div class="col">
|
||||
<div class="card text-dark mb-0">
|
||||
<div class="card-header p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-motherboard" viewBox="0 0 16 16">
|
||||
<path d="M11.5 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5Zm2 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5Zm-10 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 2a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6ZM5 3a1 1 0 0 0-1 1h-.5a.5.5 0 0 0 0 1H4v1h-.5a.5.5 0 0 0 0 1H4a1 1 0 0 0 1 1v.5a.5.5 0 0 0 1 0V8h1v.5a.5.5 0 0 0 1 0V8a1 1 0 0 0 1-1h.5a.5.5 0 0 0 0-1H9V5h.5a.5.5 0 0 0 0-1H9a1 1 0 0 0-1-1v-.5a.5.5 0 0 0-1 0V3H6v-.5a.5.5 0 0 0-1 0V3Zm0 1h3v3H5V4Zm6.5 7a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-2Z" />
|
||||
<path d="M1 2a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-2H.5a.5.5 0 0 1-.5-.5v-1A.5.5 0 0 1 .5 9H1V8H.5a.5.5 0 0 1-.5-.5v-1A.5.5 0 0 1 .5 6H1V5H.5a.5.5 0 0 1-.5-.5v-2A.5.5 0 0 1 .5 2H1Zm1 11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v11Z" />
|
||||
</svg> <strong>SYSTEM STATUS</strong>
|
||||
<i class="bi bi-sliders2" style="font-size: 1rem; color: black;"></i> <strong>TNC SETTINGS</strong>
|
||||
</div>
|
||||
<div class="card-body p-2 mb-1">
|
||||
<button class="btn btn-secondary btn-sm" id="python_version" type="button" disabled>Python</button>
|
||||
<button class="btn btn-secondary btn-sm" id="node_version" type="button" disabled>Node</button>
|
||||
<button class="btn btn-secondary btn-sm" id="hamlib_version" type="button" disabled>Hamlib</button>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="fftSwitch">
|
||||
<label class="form-check-label" for="fftSwitch">Waterfall</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="scatterSwitch">
|
||||
<label class="form-check-label" for="scatterSwitch">Scatter</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="500HzModeSwitch">
|
||||
<label class="form-check-label" for="500HzModeSwitch">500Hz</label>
|
||||
</div>
|
||||
<!--<button class="btn btn-secondary btn-sm" id="python_version" type="button" disabled>Python</button>-->
|
||||
<!--<button class="btn btn-secondary btn-sm" id="node_version" type="button" disabled>Node</button>-->
|
||||
<!--<button class="btn btn-secondary btn-sm" id="hamlib_version" type="button" disabled>Hamlib</button>-->
|
||||
<!--<button class="btn btn-secondary btn-sm" id="operating_system" type="button" disabled>OS</button>-->
|
||||
<button class="btn btn-secondary btn-sm" id="cpu_load_button" type="button" disabled>
|
||||
<!--<button class="btn btn-secondary btn-sm" id="cpu_load_button" type="button" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cpu" viewBox="0 0 16 16">
|
||||
<path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z" />
|
||||
</svg> <span id="cpu_load">---</span>
|
||||
|
@ -713,17 +778,17 @@
|
|||
<path d="M1 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4.586a1 1 0 0 0 .707-.293l.353-.353a.5.5 0 0 1 .708 0l.353.353a1 1 0 0 0 .707.293H15a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H1Zm.5 1h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5Zm5 0h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5Zm4.5.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4ZM2 10v2H1v-2h1Zm2 0v2H3v-2h1Zm2 0v2H5v-2h1Zm3 0v2H8v-2h1Zm2 0v2h-1v-2h1Zm2 0v2h-1v-2h1Zm2 0v2h-1v-2h1Z" />
|
||||
</svg> <span id="ram_load">---</span>
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<hr class="m-1">-->
|
||||
<div class="container mt-2 p-0">
|
||||
<div class="row collapse multi-collapse" id="collapseThirdRow">
|
||||
<div class="col-5">
|
||||
<div class="card text-dark mb-1">
|
||||
<div class="card-header p-1"><strong>AUDIO LEVEL</strong>
|
||||
<div class="card-header p-1"><i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i> <strong>AUDIO LEVEL</strong>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="progress mb-1" style="height: 20px;">
|
||||
|
@ -740,26 +805,33 @@
|
|||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Send a ping request to a remote station"> <span class="input-group-text">Ping</span>
|
||||
<input type="text" class="form-control" style="max-width: 6rem; text-transform:uppercase" placeholder="DXcall" pattern="[A-Z]*" id="dxCall" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<button class="btn btn-success" id="sendPing" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-arrows-angle-contract" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M.172 15.828a.5.5 0 0 0 .707 0l4.096-4.096V14.5a.5.5 0 1 0 1 0v-3.975a.5.5 0 0 0-.5-.5H1.5a.5.5 0 0 0 0 1h2.768L.172 15.121a.5.5 0 0 0 0 .707zM15.828.172a.5.5 0 0 0-.707 0l-4.096 4.096V1.5a.5.5 0 1 0-1 0v3.975a.5.5 0 0 0 .5.5H14.5a.5.5 0 0 0 0-1h-2.768L15.828.879a.5.5 0 0 0 0-.707z" />
|
||||
</svg>
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input type="text" class="form-control" style="max-width: 6rem; text-transform:uppercase" placeholder="DXcall" pattern="[A-Z]*" id="dxCall" maxlength="11" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<button class="btn btn-sm btn-primary" id="sendPing" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Send a ping request to a remote station">
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-success" id="openARQSession" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="connect to a remote station">
|
||||
<i class="bi bi-arrows-angle-contract" style="font-size: 0.8rem; color: white;"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-danger" id="closeARQSession" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="disconnect from a remote station">
|
||||
<i class="bi bi-arrows-angle-expand" style="font-size: 0.8rem; color: white;"></i>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Send a CQ to the world">
|
||||
<button class="btn btn-success" id="sendCQ" type="button">CQ CQ CQ</button>
|
||||
<button class="btn btn-sm btn-success" id="sendCQ" type="button">CQ CQ</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Start or stop the Beacon mode. You can also set the interval. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is openend, the beacon pauses.">
|
||||
<button type="button" id="startBeacon" class="btn btn-success">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-arrow-repeat" viewBox="0 0 16 16">
|
||||
<path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z" />
|
||||
<path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z" />
|
||||
</svg>
|
||||
<button type="button" id="startBeacon" class="btn btn-sm btn-success">
|
||||
<i class="bi bi-arrow-clockwise" style="font-size: 0.8rem; color: white;"></i>
|
||||
</button>
|
||||
<div class="input-group-text p-1">Beacon</div>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="beaconInterval" style="width:5rem">
|
||||
|
@ -769,11 +841,17 @@
|
|||
<option value="30">30s</option>
|
||||
<option value="45">45s</option>
|
||||
<option value="60">60s</option>
|
||||
<option value="90">90s</option>+</select>
|
||||
<option value="90">90s</option>
|
||||
<option value="120">2min</option>
|
||||
<option value="300">5min</option>
|
||||
<option value="600">10min</option>
|
||||
<option value="900">15min</option>
|
||||
<option value="1800">30min</option>
|
||||
<option value="3600">60min</option>
|
||||
</select>
|
||||
<button type="button" id="stopBeacon" class="btn btn-sm btn-danger">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-x-octagon-fill" viewBox="0 0 16 16">
|
||||
<path d="M11.46.146A.5.5 0 0 0 11.107 0H4.893a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146zm-6.106 4.5L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z" />
|
||||
</svg>
|
||||
<i class="bi bi-x-octagon-fill" style="font-size: 0.8rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -795,6 +873,7 @@
|
|||
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch2"><strong>SCATTER</strong>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary" id="channel_busy" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>">busy</button>
|
||||
</div>
|
||||
<div class="card-body p-1" style="height: 200px">
|
||||
<!--278px-->
|
||||
|
@ -807,9 +886,8 @@
|
|||
<div class="card text-dark mb-1" style="height: 240px">
|
||||
<!--325px-->
|
||||
<div class="card-header p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-columns-reverse" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M0 .5A.5.5 0 0 1 .5 0h2a.5.5 0 0 1 0 1h-2A.5.5 0 0 1 0 .5Zm4 0a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1h-10A.5.5 0 0 1 4 .5Zm-4 2A.5.5 0 0 1 .5 2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm-4 2A.5.5 0 0 1 .5 4h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5Zm-4 2A.5.5 0 0 1 .5 6h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Zm-4 2A.5.5 0 0 1 .5 8h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Zm-4 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1h-10a.5.5 0 0 1-.5-.5Zm-4 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5Zm-4 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm4 0a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5Z" />
|
||||
</svg><strong> HEARD STATIONS</strong>
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1rem; color: black;" style="font-size: 1rem; color: black;"></i>
|
||||
<strong> HEARD STATIONS</strong>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
|
@ -847,6 +925,9 @@
|
|||
<!------------------------------- RECEIVED FILES SIDEBAR ----------------------->
|
||||
<div class="offcanvas offcanvas-end" tabindex="-1" id="receivedFilesSidebar" aria-labelledby="receivedFilesSidebarLabel">
|
||||
<div class="offcanvas-header p-2">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openReceivedFilesFolder" type="button">
|
||||
<i class="bi bi-folder2-open" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
<h5 id="receivedFilesSidebarLabel">
|
||||
Received Files
|
||||
</h5>
|
||||
|
@ -898,7 +979,7 @@
|
|||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input type="text" class="form-control" style="max-width: 6rem; text-transform:uppercase" pattern="[A-Z]" placeholder="DXcall" id="dataModalDxCall" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<input type="text" class="form-control" style="max-width: 6rem; text-transform:uppercase" pattern="[A-Z]" placeholder="DXcall" id="dataModalDxCall" maxlength="11" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
|
@ -940,18 +1021,17 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1">Mode</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="datamode">
|
||||
<!--<option value="14">low SNR (DC0)</option>-->
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="datamode" disabled>
|
||||
<option selected value="255">AUTO</option>
|
||||
<option value="10">HIGH SNR (DC1)</option>
|
||||
<option value="12">MED SNR (DC3)</option>
|
||||
<option value="14">LOW SNR (DC0)</option>
|
||||
<!--<option value="232">HIGH SNR (DC1)</option>-->
|
||||
<!--<option value="231">MED SNR (DC3)</option>-->
|
||||
<!--<option value="230">LOW SNR (DC0)</option>-->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1">Frames</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="framesperburst">
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="framesperburst" disabled>
|
||||
<option selected value="1">1</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -965,10 +1045,10 @@
|
|||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="col">
|
||||
<button type="button" id="startTransmission" data-bs-dismiss="offcanvas" class="btn btn-success" style="width:100%" disabled>START TRANSMISSION</button>
|
||||
<button type="button" id="startTransmission" data-bs-dismiss="offcanvas" class="btn btn-success" style="width:100%">START TRANSMISSION</button>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<button type="button" id="stopTransmission" class="btn btn-danger" style="width:100%" disabled>STOP</button>
|
||||
<button type="button" id="stopTransmission" class="btn btn-danger" style="width:100%">STOP</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
|
@ -1000,24 +1080,30 @@
|
|||
<div class="container-fluid">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group btn-group-sm me-2" role="group">
|
||||
<button class="btn btn-secondary" id="ptt_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="PTT state:<strong class='text-success'>RECEIVING</strong> / <strong class='text-danger'>TRANSMITTING</strong>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-broadcast-pin" viewBox="0 0 16 16">
|
||||
<path d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707zm2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708zm5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708zm2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM6 8a2 2 0 1 1 2.5 1.937V15.5a.5.5 0 0 1-1 0V9.937A2 2 0 0 1 6 8z" />
|
||||
</svg>
|
||||
<button class="btn btn-sm btn-secondary" id="ptt_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="PTT state:<strong class='text-success'>RECEIVING</strong> / <strong class='text-danger'>TRANSMITTING</strong>">
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-2" role="group">
|
||||
<button class="btn btn-secondary" id="busy_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="TNC busy state: <strong class='text-success'>IDLE</strong> / <strong class='text-danger'>BUSY</strong>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cpu" viewBox="0 0 16 16">
|
||||
<path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z" />
|
||||
</svg>
|
||||
<button class="btn btn-sm btn-secondary" id="busy_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="TNC busy state: <strong class='text-success'>IDLE</strong> / <strong class='text-danger'>BUSY</strong>">
|
||||
<i class="bi bi-cpu" style="font-size: 0.8rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-2" role="group">
|
||||
<button class="btn btn-secondary" id="arq_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="DATA-CHANNEL state: <strong class='text-warning'>OPEN</strong>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z" />
|
||||
</svg>
|
||||
<button class="btn btn-sm btn-secondary" id="arq_session" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="ARQ SESSION state: <strong class='text-warning'>OPEN</strong>">
|
||||
<i class="bi bi-arrow-left-right" style="font-size: 0.8rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="btn-group btn-group-sm me-2" role="group">
|
||||
<button class="btn btn-sm btn-secondary" id="arq_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="DATA-CHANNEL state: <strong class='text-warning'>OPEN</strong>">
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 0.8rem; color: white;"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1029,13 +1115,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container-fluid p-0" style="width:12rem">
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><strong>B/min</strong></span>
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><i class="bi bi-speedometer2" style="font-size: 1rem; color: black;"></i></span>
|
||||
<span class="input-group-text" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="actual speed level">
|
||||
<i id="speed_level" class="bi bi-reception-0" style="font-size: 1rem; color: black;"></i></span>
|
||||
|
||||
|
||||
<span class="input-group-text" id="bytes_per_min" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="raw data rate modem in bytes per minute">---</span>
|
||||
|
||||
<span class="input-group-text" id="bytes_per_min_compressed" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="data rate including file compression in bytes per minute">---</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid p-0" style="width:10rem">
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><strong>kBytes</strong></span>
|
||||
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><strong>Bytes</strong></span>
|
||||
<span class="input-group-text" id="total_bytes">---</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1058,6 +1149,67 @@
|
|||
<script src="waterfall/spectrum.js"></script>
|
||||
<script src="waterfall/spectrogram.js"></script>
|
||||
<!--<script src="waterfall/script.js"></script>-->
|
||||
|
||||
|
||||
<!-- INFO MODAL -->
|
||||
<div class="modal fade" data-bs-backdrop="static" tabindex="-1" id="infoModal">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">App settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">Theme</span>
|
||||
<select class="form-select form-select-sm" id="theme_selector">
|
||||
<option value="default">Default</option>
|
||||
<option value="cerulean">Cerulean</option>
|
||||
<option value="cosmo">Cosmo</option>
|
||||
<option value="cyborg">Cyborg</option>
|
||||
<option value="darkly">Darkly</option>
|
||||
<option value="flatly">Flatly</option>
|
||||
<option value="journal">Journal</option>
|
||||
<option value="litera">Litera</option>
|
||||
<option value="lumen">Lumen</option>
|
||||
<option value="lux">Lux</option>
|
||||
<option value="materia">Materia</option>
|
||||
<option value="minty">Minty</option>
|
||||
<option value="morph">Morhp</option>
|
||||
<option value="pulse">Pulse</option>
|
||||
<option value="quartz">Quartz</option>
|
||||
<option value="sandstone">Sandstone</option>
|
||||
<option value="simplex">Simplex</option>
|
||||
<option value="sketchy">Sketchy</option>
|
||||
<option value="slate">Slate</option>
|
||||
<option value="solar">Solar</option>
|
||||
<option value="spacelab">Spacelab</option>
|
||||
<option value="superhero">Superhero</option>
|
||||
<option value="united">United</option>
|
||||
<option value="vapor">Vapor</option>
|
||||
<option value="yeti">Yeti</option>
|
||||
<option value="zephyr">Zephyr</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">Update channel</span>
|
||||
<select class="form-select form-select-sm" id="update_channel_selector">
|
||||
<option value="latest">stable</option>
|
||||
<option value="beta">beta</option>
|
||||
<option value="alpha">alpha</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text" for="inputGroupFile02">Received files folder</label>
|
||||
<input type="text" class="form-control" id="received_files_folder">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- HAMLIB ADVANCED SETTINGS MODAL -->
|
||||
<div class="modal fade" data-bs-backdrop="static" tabindex="-1" id="advancedHamlibSettingsModal">
|
||||
<div class="modal-dialog">
|
||||
|
@ -1069,7 +1221,7 @@
|
|||
<div class="modal-body">
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">Port</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_deviceport_advanced">
|
||||
<!--<option selected value="/dev/ttyUSB0">/dev/ttyUSB0</option>
|
||||
<!--<option value="None">None</option>-->
|
||||
<option value="/dev/ttyUSB1">/dev/ttyUSB1</option>-->
|
||||
</select> <span class="input-group-text" id="basic-addon1">Speed</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_serialspeed_advanced">
|
||||
|
@ -1087,11 +1239,12 @@
|
|||
</div>
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">PTT</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_ptt_protocol_advanced" style="width: 0.5rem">
|
||||
<option value="NONE">NONE</option>
|
||||
<option value="RIG">RIG</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="RTS">Serial RTS</option>
|
||||
<option value="DTR-H">Serial DTR-High</option>
|
||||
<option value="DTR-H">Serial DTR-Low</option>
|
||||
<option value="DTR-L">Serial DTR-Low</option>
|
||||
<option value="PARALLEL">Rig PARALLEL</option>
|
||||
<option value="MICDATA">Rig MICDATA</option>
|
||||
<option value="CM108">Rig CM108</option>
|
||||
|
@ -1116,7 +1269,9 @@
|
|||
</div>
|
||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text" id="basic-addon1">PTT Port</span>
|
||||
<select class="form-select form-select-sm" aria-label=".form-select-sm" id="hamlib_ptt_port_advanced">
|
||||
<!--<option selected value="/dev/ttyUSB0">/dev/ttyUSB0</option>
|
||||
<option value="None">None</option>
|
||||
</select>
|
||||
<!--
|
||||
<option value="/dev/ttyUSB1">/dev/ttyUSB1</option>-->
|
||||
<!--
|
||||
</select> <span class="input-group-text" id="basic-addon1">PTT Speed</span>
|
||||
|
|
40
gui/src/log-module.html
Normal file
40
gui/src/log-module.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
|
||||
<title>FreeDATA - Live Log</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Log entry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="log">
|
||||
<!--
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Mark</td>
|
||||
<td>Otto</td>
|
||||
<td>@mdo</td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
1
test/in8.raw
Normal file
1
test/in8.raw
Normal file
File diff suppressed because one or more lines are too long
BIN
test/out48.raw
Normal file
BIN
test/out48.raw
Normal file
Binary file not shown.
BIN
test/out8.raw
Normal file
BIN
test/out8.raw
Normal file
Binary file not shown.
107
tnc/audio.py
Normal file
107
tnc/audio.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
|
||||
import json
|
||||
import sys
|
||||
import multiprocessing
|
||||
|
||||
####################################################
|
||||
# https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
# we need to have a look at this if we want to run this on Windows and MacOS !
|
||||
# Currently it seems, this is a Linux-only problem
|
||||
from ctypes import *
|
||||
from contextlib import contextmanager
|
||||
import pyaudio
|
||||
|
||||
|
||||
ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)
|
||||
|
||||
def py_error_handler(filename, line, function, err, fmt):
|
||||
"""
|
||||
|
||||
Args:
|
||||
filename:
|
||||
line:
|
||||
function:
|
||||
err:
|
||||
fmt:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
|
||||
|
||||
@contextmanager
|
||||
def noalsaerr():
|
||||
""" """
|
||||
asound = cdll.LoadLibrary('libasound.so')
|
||||
asound.snd_lib_error_set_handler(c_error_handler)
|
||||
yield
|
||||
asound.snd_lib_error_set_handler(None)
|
||||
|
||||
# with noalsaerr():
|
||||
# p = pyaudio.PyAudio()
|
||||
#####################################################
|
||||
|
||||
def get_audio_devices():
|
||||
"""
|
||||
return list of input and output audio devices in own process to avoid crashes of portaudio on raspberry pi
|
||||
|
||||
also uses a process data manager
|
||||
"""
|
||||
# we need to run this on windows for multiprocessing support
|
||||
# multiprocessing.freeze_support()
|
||||
#multiprocessing.get_context('spawn')
|
||||
|
||||
with multiprocessing.Manager() as manager:
|
||||
proxy_input_devices = manager.list()
|
||||
proxy_output_devices = manager.list()
|
||||
#print(multiprocessing.get_start_method())
|
||||
p = multiprocessing.Process(target=fetch_audio_devices, args=(proxy_input_devices, proxy_output_devices))
|
||||
p.start()
|
||||
p.join()
|
||||
|
||||
return list(proxy_input_devices), list(proxy_output_devices)
|
||||
|
||||
def fetch_audio_devices(input_devices, output_devices):
|
||||
"""
|
||||
get audio devices from portaudio
|
||||
|
||||
Args:
|
||||
input_devices: proxy variable for input devices
|
||||
output_devices: proxy variable for outout devices
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# UPDATE LIST OF AUDIO DEVICES
|
||||
try:
|
||||
# we need to "try" this, because sometimes libasound.so isn't in the default place
|
||||
# try to supress error messages
|
||||
with noalsaerr(): # https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
p = pyaudio.PyAudio()
|
||||
# else do it the default way
|
||||
except Exception as e:
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
#input_devices = []
|
||||
#output_devices = []
|
||||
|
||||
for i in range(0, p.get_device_count()):
|
||||
# we need to do a try exception, beacuse for windows theres no audio device range
|
||||
try:
|
||||
maxInputChannels = p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')
|
||||
maxOutputChannels = p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')
|
||||
name = p.get_device_info_by_host_api_device_index(0, i).get('name')
|
||||
except:
|
||||
maxInputChannels = 0
|
||||
maxOutputChannels = 0
|
||||
name = ''
|
||||
|
||||
if maxInputChannels > 0:
|
||||
input_devices.append({"id": i, "name": str(name)})
|
||||
if maxOutputChannels > 0:
|
||||
output_devices.append({"id": i, "name": str(name)})
|
||||
|
||||
p.terminate()
|
120
tnc/codec2.py
120
tnc/codec2.py
|
@ -5,14 +5,18 @@ import ctypes
|
|||
from ctypes import *
|
||||
import sys
|
||||
import os
|
||||
#import pathlib
|
||||
from enum import Enum
|
||||
import numpy as np
|
||||
#print("loading codec2 module", file=sys.stderr)
|
||||
from threading import Lock
|
||||
import glob
|
||||
import structlog
|
||||
|
||||
|
||||
# Enum for codec2 modes
|
||||
class FREEDV_MODE(Enum):
|
||||
"""
|
||||
enum for codec2 modes and names
|
||||
"""
|
||||
datac0 = 14
|
||||
datac1 = 10
|
||||
datac3 = 12
|
||||
|
@ -20,10 +24,26 @@ class FREEDV_MODE(Enum):
|
|||
|
||||
# function for returning the mode value
|
||||
def freedv_get_mode_value_by_name(mode):
|
||||
"""
|
||||
get the codec2 mode by entering its string
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns: int
|
||||
|
||||
"""
|
||||
return FREEDV_MODE[mode].value
|
||||
|
||||
# function for returning the mode name
|
||||
def freedv_get_mode_name_by_value(mode):
|
||||
"""
|
||||
get the codec2 mode name as string
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns: string
|
||||
|
||||
"""
|
||||
return FREEDV_MODE(mode).name
|
||||
|
||||
|
||||
|
@ -34,48 +54,41 @@ except:
|
|||
app_path = os.path.abspath(".")
|
||||
sys.path.append(app_path)
|
||||
|
||||
# -------------------------------------------- LOAD FREEDV
|
||||
# codec2 search pathes in descending order
|
||||
# libcodec2.so ctests
|
||||
# pathlib.Path("codec2/build_linux/src/libcodec2.so.1.0") manual build
|
||||
# pathlib.Path("lib/codec2/linux/libcodec2.so.1.0") precompiled
|
||||
# pathlib.Path("../../tnc/codec2/build_linux/src/libcodec2.so.1.0") external loading manual build
|
||||
# pathlib.Path("../../tnc/lib/codec2/linux/libcodec2.so.1.0") external loading precompiled
|
||||
|
||||
structlog.get_logger("structlog").info("[C2 ] Searching for libcodec2...")
|
||||
if sys.platform == 'linux':
|
||||
libname = ["libcodec2.so", \
|
||||
os.path.join(app_path, "codec2/build_linux/src/libcodec2.so.1.0"), \
|
||||
os.path.join(app_path, "lib/codec2/linux/libcodec2.so.1.0"), \
|
||||
os.path.join(app_path, "../tnc/codec2/build_linux/src/libcodec2.so.1.0"), \
|
||||
os.path.join(app_path, "../tnc/lib/codec2/linux/libcodec2.so.1.0"), \
|
||||
]
|
||||
files = glob.glob('**/*libcodec2*',recursive=True)
|
||||
files.append('libcodec2.so')
|
||||
elif sys.platform == 'darwin':
|
||||
files = glob.glob('**/*libcodec2*.dylib',recursive=True)
|
||||
|
||||
elif sys.platform == 'win32' or sys.platform == 'win64':
|
||||
libname = [app_path + "\\lib\\codec2\\windows\\libcodec2.dll", \
|
||||
]
|
||||
files = glob.glob('**\*libcodec2*.dll',recursive=True)
|
||||
else:
|
||||
print(f"[C2 ] Platform not supported {sys.platform}", file=sys.stderr)
|
||||
files = []
|
||||
|
||||
# iterate through codec2 search pathes
|
||||
for i in libname:
|
||||
|
||||
for file in files:
|
||||
try:
|
||||
# this is not working for all OS. Specially windows has some more problems. We need to fix this somehow.
|
||||
api = ctypes.CDLL(i)
|
||||
|
||||
print(f"[C2 ] Codec2 library found - {i}", file=sys.stderr)
|
||||
api = ctypes.CDLL(file)
|
||||
structlog.get_logger("structlog").info("[C2 ] Libcodec2 loaded", path=file)
|
||||
break
|
||||
except:
|
||||
print(f"[C2 ] Codec2 library not found - {i}", file=sys.stderr)
|
||||
pass
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").warning("[C2 ] Libcodec2 found but not loaded", path=file, e=e)
|
||||
|
||||
|
||||
# quit module if codec2 cant be loaded
|
||||
if not 'api' in locals():
|
||||
print(f"[C2 ] Loading Codec2 library failed", file=sys.stderr)
|
||||
|
||||
structlog.get_logger("structlog").critical("[C2 ] Libcodec2 not loaded", path=file)
|
||||
os._exit(1)
|
||||
|
||||
|
||||
|
||||
|
||||
# ctypes function init
|
||||
|
||||
#api.freedv_set_tuning_range.restype = c_int
|
||||
#api.freedv_set_tuning_range.argype = [c_void_p, c_float, c_float]
|
||||
|
||||
api.freedv_open.argype = [c_int]
|
||||
api.freedv_open.restype = c_void_p
|
||||
|
||||
|
@ -153,15 +166,29 @@ api.rx_sync_flags_to_text = [
|
|||
# audio buffer ---------------------------------------------------------
|
||||
|
||||
class audio_buffer:
|
||||
"""
|
||||
thread safe audio buffer, which fits to needs of codec2
|
||||
|
||||
made by David Rowe, VK5DGR
|
||||
"""
|
||||
# a buffer of int16 samples, using a fixed length numpy array self.buffer for storage
|
||||
# self.nbuffer is the current number of samples in the buffer
|
||||
def __init__(self, size):
|
||||
print("create audio_buffer: ", size)
|
||||
structlog.get_logger("structlog").debug("[C2 ] creating audio buffer", size=size)
|
||||
self.size = size
|
||||
self.buffer = np.zeros(size, dtype=np.int16)
|
||||
self.nbuffer = 0
|
||||
self.mutex = Lock()
|
||||
def push(self,samples):
|
||||
"""
|
||||
Push new data to buffer
|
||||
|
||||
Args:
|
||||
samples:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.mutex.acquire()
|
||||
# add samples at the end of the buffer
|
||||
assert self.nbuffer+len(samples) <= self.size
|
||||
|
@ -169,6 +196,14 @@ class audio_buffer:
|
|||
self.nbuffer += len(samples)
|
||||
self.mutex.release()
|
||||
def pop(self,size):
|
||||
"""
|
||||
get data from buffer in size of NIN
|
||||
Args:
|
||||
size:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.mutex.acquire()
|
||||
# remove samples from the start of the buffer
|
||||
self.nbuffer -= size;
|
||||
|
@ -185,17 +220,29 @@ api.fdmdv_8_to_48_short.argtype = [c_void_p, c_void_p, c_int]
|
|||
api.fdmdv_48_to_8_short.argtype = [c_void_p, c_void_p, c_int]
|
||||
|
||||
class resampler:
|
||||
"""
|
||||
resampler class
|
||||
"""
|
||||
# resample an array of variable length, we just store the filter memories here
|
||||
MEM8 = api.FDMDV_OS_TAPS_48_8K
|
||||
MEM48 = api.FDMDV_OS_TAPS_48K
|
||||
|
||||
def __init__(self):
|
||||
print("create 48<->8 kHz resampler")
|
||||
structlog.get_logger("structlog").debug("[C2 ] create 48<->8 kHz resampler")
|
||||
self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16)
|
||||
self.filter_mem48 = np.zeros(self.MEM48)
|
||||
|
||||
|
||||
def resample48_to_8(self,in48):
|
||||
"""
|
||||
audio resampler integration from codec2
|
||||
downsample audio from 48000Hz to 8000Hz
|
||||
Args:
|
||||
in48: input data as np.int16
|
||||
|
||||
Returns: downsampled 8000Hz data as np.int16
|
||||
|
||||
"""
|
||||
assert in48.dtype == np.int16
|
||||
# length of input vector must be an integer multiple of api.FDMDV_OS_48
|
||||
assert(len(in48) % api.FDMDV_OS_48 == 0)
|
||||
|
@ -217,6 +264,15 @@ class resampler:
|
|||
return out8
|
||||
|
||||
def resample8_to_48(self,in8):
|
||||
"""
|
||||
audio resampler integration from codec2
|
||||
resample audio from 8000Hz to 48000Hz
|
||||
Args:
|
||||
in8: input data as np.int16
|
||||
|
||||
Returns: 48000Hz audio as np.int16
|
||||
|
||||
"""
|
||||
assert in8.dtype == np.int16
|
||||
|
||||
# concat filter memory and input samples
|
||||
|
|
492
tnc/daemon.py
492
tnc/daemon.py
|
@ -5,6 +5,8 @@ daemon.py
|
|||
|
||||
Author: DJ2LS, January 2022
|
||||
|
||||
daemon for providing basic information for the tnc like audio or serial devices
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
@ -23,217 +25,202 @@ import structlog
|
|||
import log_handler
|
||||
import helpers
|
||||
import os
|
||||
import queue
|
||||
import audio
|
||||
import sock
|
||||
import atexit
|
||||
import signal
|
||||
import multiprocessing
|
||||
|
||||
log_handler.setup_logging("daemon")
|
||||
structlog.get_logger("structlog").info("[DMN] Starting FreeDATA daemon", author="DJ2LS", year="2022", version="0.1")
|
||||
# signal handler for closing aplication
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
signal handler for closing the network socket on app exit
|
||||
Args:
|
||||
sig:
|
||||
frame:
|
||||
|
||||
# get python version, which is needed later for determining installation path
|
||||
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1])
|
||||
structlog.get_logger("structlog").info("[DMN] Python", version=python_version)
|
||||
Returns: system exit
|
||||
|
||||
|
||||
####################################################
|
||||
# https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
# we need to have a look at this if we want to run this on Windows and MacOS !
|
||||
# Currently it seems, this is a Linux-only problem
|
||||
|
||||
from ctypes import *
|
||||
from contextlib import contextmanager
|
||||
import pyaudio
|
||||
|
||||
ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)
|
||||
|
||||
def py_error_handler(filename, line, function, err, fmt):
|
||||
pass
|
||||
|
||||
c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
|
||||
|
||||
@contextmanager
|
||||
def noalsaerr():
|
||||
|
||||
asound = cdll.LoadLibrary('libasound.so')
|
||||
asound.snd_lib_error_set_handler(c_error_handler)
|
||||
yield
|
||||
asound.snd_lib_error_set_handler(None)
|
||||
|
||||
# with noalsaerr():
|
||||
# p = pyaudio.PyAudio()
|
||||
######################################################
|
||||
"""
|
||||
print('Closing daemon...')
|
||||
sock.CLOSE_SIGNAL = True
|
||||
sys.exit(0)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
|
||||
|
||||
# load crc engine
|
||||
crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc8 library
|
||||
class DAEMON():
|
||||
"""
|
||||
daemon class
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
|
||||
# load crc engine
|
||||
self.crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc8 library
|
||||
|
||||
self.daemon_queue = sock.DAEMON_QUEUE
|
||||
update_audio_devices = threading.Thread(target=self.update_audio_devices, name="UPDATE_AUDIO_DEVICES", daemon=True)
|
||||
update_audio_devices.start()
|
||||
|
||||
update_serial_devices = threading.Thread(target=self.update_serial_devices, name="UPDATE_SERIAL_DEVICES", daemon=True)
|
||||
update_serial_devices.start()
|
||||
|
||||
worker = threading.Thread(target=self.worker, name="WORKER", daemon=True)
|
||||
worker.start()
|
||||
|
||||
|
||||
def start_daemon():
|
||||
|
||||
def update_audio_devices(self):
|
||||
"""
|
||||
update audio devices and set to static
|
||||
"""
|
||||
while 1:
|
||||
try:
|
||||
structlog.get_logger("structlog").info("[DMN] Starting TCP/IP socket", port=PORT)
|
||||
# https://stackoverflow.com/a/16641793
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
daemon = socketserver.TCPServer(('0.0.0.0', PORT), CMDTCPRequestHandler)
|
||||
daemon.serve_forever()
|
||||
if not static.TNCSTARTED:
|
||||
|
||||
finally:
|
||||
structlog.get_logger("structlog").warning("[DMN] Closing socket", port=PORT)
|
||||
daemon.server_close()
|
||||
static.AUDIO_INPUT_DEVICES, static.AUDIO_OUTPUT_DEVICES = audio.get_audio_devices()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
class CMDTCPRequestHandler(socketserver.BaseRequestHandler):
|
||||
def update_serial_devices(self):
|
||||
"""
|
||||
update serial devices and set to static
|
||||
"""
|
||||
while 1:
|
||||
try:
|
||||
#print("update serial")
|
||||
serial_devices = []
|
||||
ports = serial.tools.list_ports.comports()
|
||||
for port, desc, hwid in ports:
|
||||
|
||||
def handle(self, hamlib_version = 0):
|
||||
structlog.get_logger("structlog").debug("[DMN] Client connected", ip=self.client_address[0])
|
||||
# calculate hex of hwid if we have unique names
|
||||
crc_hwid = self.crc_algorithm(bytes(hwid, encoding='utf-8'))
|
||||
crc_hwid = crc_hwid.to_bytes(2, byteorder='big')
|
||||
crc_hwid = crc_hwid.hex()
|
||||
description = desc + ' [' + crc_hwid + ']'
|
||||
serial_devices.append({"port": str(port), "description": str(description) })
|
||||
|
||||
# loop through socket buffer until timeout is reached. then close buffer
|
||||
socketTimeout = time.time() + 6
|
||||
while socketTimeout > time.time():
|
||||
static.SERIAL_DEVICES = serial_devices
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
time.sleep(0.01)
|
||||
encoding = 'utf-8'
|
||||
#data = str(self.request.recv(1024), 'utf-8')
|
||||
|
||||
data = bytes()
|
||||
|
||||
# we need to loop through buffer until end of chunk is reached or timeout occured
|
||||
while socketTimeout > time.time():
|
||||
data += self.request.recv(64)
|
||||
# or chunk.endswith(b'\n'):
|
||||
if data.startswith(b'{"type"') and data.endswith(b'}\n'):
|
||||
break
|
||||
data = data[:-1] # remove b'\n'
|
||||
data = str(data, encoding)
|
||||
|
||||
if len(data) > 0:
|
||||
# reset socket timeout
|
||||
socketTimeout = time.time() + static.SOCKET_TIMEOUT
|
||||
# only read first line of string. multiple lines will cause an json error
|
||||
# this occurs possibly, if we are getting data too fast
|
||||
# data = data.splitlines()[0]
|
||||
data = data.splitlines()[0]
|
||||
|
||||
|
||||
# we need to do some error handling in case of socket timeout or decoding issue
|
||||
def worker(self):
|
||||
"""
|
||||
a worker for the received commands
|
||||
"""
|
||||
while 1:
|
||||
try:
|
||||
|
||||
# convert data to json object
|
||||
received_json = json.loads(data)
|
||||
data = self.daemon_queue.get()
|
||||
|
||||
# GET COMMANDS
|
||||
# "command" : "..."
|
||||
# data[1] mycall
|
||||
# data[2] mygrid
|
||||
# data[3] rx_audio
|
||||
# data[4] tx_audio
|
||||
# data[5] devicename
|
||||
# data[6] deviceport
|
||||
# data[7] serialspeed
|
||||
# data[8] pttprotocol
|
||||
# data[9] pttport
|
||||
# data[10] data_bits
|
||||
# data[11] stop_bits
|
||||
# data[12] handshake
|
||||
# data[13] radiocontrol
|
||||
# data[14] rigctld_ip
|
||||
# data[15] rigctld_port
|
||||
# data[16] send_scatter
|
||||
# data[17] send_fft
|
||||
# data[18] low_bandwith_mode
|
||||
|
||||
# SET COMMANDS
|
||||
# "command" : "..."
|
||||
# "parameter" : " ..."
|
||||
|
||||
# DATA COMMANDS
|
||||
# "command" : "..."
|
||||
# "type" : "..."
|
||||
# "dxcallsign" : "..."
|
||||
# "data" : "..."
|
||||
|
||||
# print(received_json)
|
||||
# print(received_json["type"])
|
||||
# print(received_json["command"])
|
||||
# try:
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'MYCALLSIGN':
|
||||
callsign = received_json["parameter"]
|
||||
print(received_json)
|
||||
if bytes(callsign, 'utf-8') == b'':
|
||||
self.request.sendall(b'INVALID CALLSIGN')
|
||||
structlog.get_logger("structlog").warning("[DMN] SET MYCALL FAILED", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC8)
|
||||
else:
|
||||
static.MYCALLSIGN = bytes(callsign, 'utf-8')
|
||||
static.MYCALLSIGN_CRC8 = helpers.get_crc_8(static.MYCALLSIGN)
|
||||
|
||||
structlog.get_logger("structlog").info("[DMN] SET MYCALL", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC8)
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'MYGRID':
|
||||
mygrid = received_json["parameter"]
|
||||
|
||||
if bytes(mygrid, 'utf-8') == b'':
|
||||
self.request.sendall(b'INVALID GRID')
|
||||
else:
|
||||
static.MYGRID = bytes(mygrid, 'utf-8')
|
||||
structlog.get_logger("structlog").info("[DMN] SET MYGRID", grid=static.MYGRID)
|
||||
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'STARTTNC' and not static.TNCSTARTED:
|
||||
mycall = str(received_json["parameter"][0]["mycall"])
|
||||
mygrid = str(received_json["parameter"][0]["mygrid"])
|
||||
rx_audio = str(received_json["parameter"][0]["rx_audio"])
|
||||
tx_audio = str(received_json["parameter"][0]["tx_audio"])
|
||||
devicename = str(received_json["parameter"][0]["devicename"])
|
||||
deviceport = str(received_json["parameter"][0]["deviceport"])
|
||||
serialspeed = str(received_json["parameter"][0]["serialspeed"])
|
||||
pttprotocol = str(received_json["parameter"][0]["pttprotocol"])
|
||||
pttport = str(received_json["parameter"][0]["pttport"])
|
||||
data_bits = str(received_json["parameter"][0]["data_bits"])
|
||||
stop_bits = str(received_json["parameter"][0]["stop_bits"])
|
||||
handshake = str(received_json["parameter"][0]["handshake"])
|
||||
radiocontrol = str(received_json["parameter"][0]["radiocontrol"])
|
||||
rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"])
|
||||
rigctld_port = str(received_json["parameter"][0]["rigctld_port"])
|
||||
|
||||
structlog.get_logger("structlog").warning("[DMN] Starting TNC", rig=devicename, port=deviceport)
|
||||
#print(received_json["parameter"][0])
|
||||
|
||||
# command = "--rx "+ rx_audio +" \
|
||||
# --tx "+ tx_audio +" \
|
||||
# --deviceport "+ deviceport +" \
|
||||
# --deviceid "+ deviceid + " \
|
||||
# --serialspeed "+ serialspeed + " \
|
||||
# --pttprotocol "+ pttprotocol + " \
|
||||
# --pttport "+ pttport
|
||||
if data[0] == 'STARTTNC':
|
||||
structlog.get_logger("structlog").warning("[DMN] Starting TNC", rig=data[5], port=data[6])
|
||||
|
||||
# list of parameters, necessary for running subprocess command as a list
|
||||
options = []
|
||||
|
||||
options.append('--port')
|
||||
options.append(str(static.DAEMONPORT - 1))
|
||||
|
||||
options.append('--mycall')
|
||||
options.append(mycall)
|
||||
options.append(data[1])
|
||||
|
||||
options.append('--mygrid')
|
||||
options.append(mygrid)
|
||||
options.append(data[2])
|
||||
|
||||
options.append('--rx')
|
||||
options.append(rx_audio)
|
||||
options.append(data[3])
|
||||
|
||||
options.append('--tx')
|
||||
options.append(tx_audio)
|
||||
options.append('--deviceport')
|
||||
options.append(deviceport)
|
||||
options.append(data[4])
|
||||
|
||||
# if radiocontrol != disabled
|
||||
# this should hopefully avoid a ton of problems if we are just running in
|
||||
# disabled mode
|
||||
|
||||
if data[13] != 'disabled':
|
||||
|
||||
options.append('--devicename')
|
||||
options.append(devicename)
|
||||
options.append(data[5])
|
||||
|
||||
options.append('--deviceport')
|
||||
options.append(data[6])
|
||||
|
||||
options.append('--serialspeed')
|
||||
options.append(serialspeed)
|
||||
options.append(data[7])
|
||||
|
||||
options.append('--pttprotocol')
|
||||
options.append(pttprotocol)
|
||||
options.append(data[8])
|
||||
|
||||
options.append('--pttport')
|
||||
options.append(pttport)
|
||||
options.append(data[9])
|
||||
|
||||
options.append('--data_bits')
|
||||
options.append(data_bits)
|
||||
options.append(data[10])
|
||||
|
||||
options.append('--stop_bits')
|
||||
options.append(stop_bits)
|
||||
options.append(data[11])
|
||||
|
||||
options.append('--handshake')
|
||||
options.append(handshake)
|
||||
options.append(data[12])
|
||||
|
||||
options.append('--radiocontrol')
|
||||
options.append(radiocontrol)
|
||||
options.append(data[13])
|
||||
|
||||
if data[13] != 'rigctld':
|
||||
options.append('--rigctld_ip')
|
||||
options.append(rigctld_ip)
|
||||
options.append(data[14])
|
||||
|
||||
options.append('--rigctld_port')
|
||||
options.append(rigctld_port)
|
||||
options.append(data[15])
|
||||
|
||||
if data[16] == 'True':
|
||||
options.append('--scatter')
|
||||
|
||||
if data[17] == 'True':
|
||||
options.append('--fft')
|
||||
|
||||
if data[18] == 'True':
|
||||
options.append('--500hz')
|
||||
|
||||
# try running tnc from binary, else run from source
|
||||
# this helps running the tnc in a developer environment
|
||||
try:
|
||||
command = []
|
||||
if sys.platform == 'linux' or sys.platform == 'darwin':
|
||||
command.append('./tnc')
|
||||
command.append('./freedata-tnc')
|
||||
elif sys.platform == 'win32' or sys.platform == 'win64':
|
||||
command.append('tnc.exe')
|
||||
command.append('freedata-tnc.exe')
|
||||
|
||||
command += options
|
||||
p = subprocess.Popen(command)
|
||||
|
||||
atexit.register(p.kill)
|
||||
|
||||
structlog.get_logger("structlog").info("[DMN] TNC started", path="binary")
|
||||
except:
|
||||
command = []
|
||||
|
@ -245,101 +232,44 @@ class CMDTCPRequestHandler(socketserver.BaseRequestHandler):
|
|||
command.append('main.py')
|
||||
command += options
|
||||
p = subprocess.Popen(command)
|
||||
atexit.register(p.kill)
|
||||
|
||||
structlog.get_logger("structlog").info("[DMN] TNC started", path="source")
|
||||
|
||||
static.TNCPROCESS = p # .pid
|
||||
static.TNCSTARTED = True
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'STOPTNC':
|
||||
'''
|
||||
# WE HAVE THIS PART in SOCKET
|
||||
if data[0] == 'STOPTNC':
|
||||
static.TNCPROCESS.kill()
|
||||
structlog.get_logger("structlog").warning("[DMN] Stopping TNC")
|
||||
#os.kill(static.TNCPROCESS, signal.SIGKILL)
|
||||
static.TNCSTARTED = False
|
||||
'''
|
||||
# data[1] devicename
|
||||
# data[2] deviceport
|
||||
# data[3] serialspeed
|
||||
# data[4] pttprotocol
|
||||
# data[5] pttport
|
||||
# data[6] data_bits
|
||||
# data[7] stop_bits
|
||||
# data[8] handshake
|
||||
# data[9] radiocontrol
|
||||
# data[10] rigctld_ip
|
||||
# data[11] rigctld_port
|
||||
if data[0] == 'TEST_HAMLIB':
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'DAEMON_STATE':
|
||||
|
||||
data = {
|
||||
'COMMAND': 'DAEMON_STATE',
|
||||
'DAEMON_STATE': [],
|
||||
'PYTHON_VERSION': str(python_version),
|
||||
'HAMLIB_VERSION': str(hamlib_version),
|
||||
'INPUT_DEVICES': [],
|
||||
'OUTPUT_DEVICES': [],
|
||||
'SERIAL_DEVICES': [
|
||||
], "CPU": str(psutil.cpu_percent()), "RAM": str(psutil.virtual_memory().percent), "VERSION": "0.1-prototype"}
|
||||
|
||||
if static.TNCSTARTED:
|
||||
data["DAEMON_STATE"].append({"STATUS": "running"})
|
||||
else:
|
||||
data["DAEMON_STATE"].append({"STATUS": "stopped"})
|
||||
|
||||
# UPDATE LIST OF AUDIO DEVICES
|
||||
try:
|
||||
# we need to "try" this, because sometimes libasound.so isn't in the default place
|
||||
# try to supress error messages
|
||||
with noalsaerr(): # https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
p = pyaudio.PyAudio()
|
||||
# else do it the default way
|
||||
except Exception as e:
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
for i in range(0, p.get_device_count()):
|
||||
# we need to do a try exception, beacuse for windows theres now audio device range
|
||||
try:
|
||||
maxInputChannels = p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')
|
||||
maxOutputChannels = p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')
|
||||
name = p.get_device_info_by_host_api_device_index(0, i).get('name')
|
||||
except:
|
||||
maxInputChannels = 0
|
||||
maxOutputChannels = 0
|
||||
name = ''
|
||||
#crc_name = crc_algorithm(bytes(name, encoding='utf-8'))
|
||||
#crc_name = crc_name.to_bytes(2, byteorder='big')
|
||||
#crc_name = crc_name.hex()
|
||||
#name = name + ' [' + crc_name + ']'
|
||||
|
||||
if maxInputChannels > 0:
|
||||
data["INPUT_DEVICES"].append(
|
||||
{"ID": i, "NAME": str(name)})
|
||||
if maxOutputChannels > 0:
|
||||
data["OUTPUT_DEVICES"].append(
|
||||
{"ID": i, "NAME": str(name)})
|
||||
p.terminate()
|
||||
|
||||
# UPDATE LIST OF SERIAL DEVICES
|
||||
ports = serial.tools.list_ports.comports()
|
||||
for port, desc, hwid in ports:
|
||||
|
||||
# calculate hex of hwid if we have unique names
|
||||
crc_hwid = crc_algorithm(bytes(hwid, encoding='utf-8'))
|
||||
crc_hwid = crc_hwid.to_bytes(2, byteorder='big')
|
||||
crc_hwid = crc_hwid.hex()
|
||||
description = desc + ' [' + crc_hwid + ']'
|
||||
|
||||
data["SERIAL_DEVICES"].append(
|
||||
{"PORT": str(port), "DESCRIPTION": str(description) })
|
||||
|
||||
|
||||
jsondata = json.dumps(data)
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'TEST_HAMLIB':
|
||||
|
||||
try:
|
||||
print(received_json["parameter"])
|
||||
|
||||
devicename = str(received_json["parameter"][0]["devicename"])
|
||||
deviceport = str(received_json["parameter"][0]["deviceport"])
|
||||
serialspeed = str(received_json["parameter"][0]["serialspeed"])
|
||||
pttprotocol = str(received_json["parameter"][0]["pttprotocol"])
|
||||
pttport = str(received_json["parameter"][0]["pttport"])
|
||||
data_bits = str(received_json["parameter"][0]["data_bits"])
|
||||
stop_bits = str(received_json["parameter"][0]["stop_bits"])
|
||||
handshake = str(received_json["parameter"][0]["handshake"])
|
||||
radiocontrol = str(received_json["parameter"][0]["radiocontrol"])
|
||||
rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"])
|
||||
rigctld_port = str(received_json["parameter"][0]["rigctld_port"])
|
||||
devicename = data[1]
|
||||
deviceport = data[2]
|
||||
serialspeed = data[3]
|
||||
pttprotocol = data[4]
|
||||
pttport = data[5]
|
||||
data_bits = data[6]
|
||||
stop_bits = data[7]
|
||||
handshake = data[8]
|
||||
radiocontrol = data[9]
|
||||
rigctld_ip = data[10]
|
||||
rigctld_port = data[11]
|
||||
|
||||
|
||||
# check how we want to control the radio
|
||||
|
@ -350,7 +280,7 @@ class CMDTCPRequestHandler(socketserver.BaseRequestHandler):
|
|||
elif radiocontrol == 'rigctld':
|
||||
import rigctld as rig
|
||||
else:
|
||||
raise NotImplementedError
|
||||
import rigdummy as rig
|
||||
|
||||
hamlib = rig.radio()
|
||||
hamlib.open_rig(devicename=devicename, deviceport=deviceport, hamlib_ptt_type=pttprotocol, serialspeed=serialspeed, pttport=pttport, data_bits=data_bits, stop_bits=stop_bits, handshake=handshake, rigctld_ip=rigctld_ip, rigctld_port = rigctld_port)
|
||||
|
@ -359,40 +289,72 @@ class CMDTCPRequestHandler(socketserver.BaseRequestHandler):
|
|||
|
||||
hamlib.set_ptt(True)
|
||||
pttstate = hamlib.get_ptt()
|
||||
|
||||
if pttstate:
|
||||
structlog.get_logger("structlog").info("[DMN] Hamlib PTT", status = 'SUCCESS')
|
||||
data = {'COMMAND': 'TEST_HAMLIB', 'RESULT': 'SUCCESS'}
|
||||
response = {'command': 'test_hamlib', 'result': 'SUCCESS'}
|
||||
elif not pttstate:
|
||||
structlog.get_logger("structlog").warning("[DMN] Hamlib PTT", status = 'NO SUCCESS')
|
||||
data = {'COMMAND': 'TEST_HAMLIB', 'RESULT': 'NOSUCCESS'}
|
||||
response = {'command': 'test_hamlib', 'result': 'NOSUCCESS'}
|
||||
else:
|
||||
structlog.get_logger("structlog").error("[DMN] Hamlib PTT", status = 'FAILED')
|
||||
data = {'COMMAND': 'TEST_HAMLIB', 'RESULT': 'FAILED'}
|
||||
response = {'command': 'test_hamlib', 'result': 'FAILED'}
|
||||
|
||||
hamlib.set_ptt(False)
|
||||
hamlib.close_rig()
|
||||
|
||||
jsondata = json.dumps(data)
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
jsondata = json.dumps(response)
|
||||
sock.SOCKET_QUEUE.put(jsondata)
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("[DMN] Hamlib: Can't open rig", e = sys.exc_info()[0], error=e)
|
||||
print(e)
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("[DMN] Network error", error=e)
|
||||
structlog.get_logger("structlog").warning("[DMN] Closing client socket", ip=self.client_address[0], port=self.client_address[1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# we need to run this on windows for multiprocessing support
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
|
||||
# --------------------------------------------GET PARAMETER INPUTS
|
||||
PARSER = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||
PARSER.add_argument('--port', dest="socket_port",default=3001, help="Socket port", type=int)
|
||||
|
||||
PARSER = argparse.ArgumentParser(description='FreeDATA Daemon')
|
||||
PARSER.add_argument('--port', dest="socket_port",default=3001, help="Socket port in the range of 1024-65536", type=int)
|
||||
ARGS = PARSER.parse_args()
|
||||
PORT = ARGS.socket_port
|
||||
|
||||
# --------------------------------------------START CMD SERVER
|
||||
static.DAEMONPORT = ARGS.socket_port
|
||||
|
||||
DAEMON_THREAD = threading.Thread(target=start_daemon, name="daemon")
|
||||
DAEMON_THREAD.start()
|
||||
|
||||
try:
|
||||
if sys.platform == 'linux':
|
||||
logging_path = os.getenv("HOME") + '/.config/' + 'FreeDATA/' + 'daemon'
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'daemon'
|
||||
|
||||
if sys.platform == 'win32' or sys.platform == 'win64':
|
||||
logging_path = os.getenv('APPDATA') + '/' + 'FreeDATA/' + 'daemon'
|
||||
|
||||
if not os.path.exists(logging_path):
|
||||
os.makedirs(logging_path)
|
||||
log_handler.setup_logging(logging_path)
|
||||
except:
|
||||
structlog.get_logger("structlog").error("[DMN] logger init error")
|
||||
|
||||
try:
|
||||
structlog.get_logger("structlog").info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT)
|
||||
# https://stackoverflow.com/a/16641793
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
cmdserver = sock.ThreadedTCPServer((static.HOST, static.DAEMONPORT), sock.ThreadedTCPRequestHandler)
|
||||
server_thread = threading.Thread(target=cmdserver.serve_forever)
|
||||
server_thread.daemon = True
|
||||
server_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("[DMN] Starting TCP/IP socket failed", port=static.DAEMONPORT, e=e)
|
||||
os._exit(1)
|
||||
daemon = DAEMON()
|
||||
|
||||
|
||||
structlog.get_logger("structlog").info("[DMN] Starting FreeDATA Daemon", author="DJ2LS", year="2022", version=static.VERSION)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
|
1171
tnc/data_handler.py
1171
tnc/data_handler.py
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@ daemon_exe = EXE(daemon_pyz,
|
|||
daemon_a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='daemon',
|
||||
name='freedata-daemon',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
@ -36,8 +36,8 @@ daemon_exe = EXE(daemon_pyz,
|
|||
entitlements_file=None )
|
||||
|
||||
# add lib folder to system path. We only need to do this once
|
||||
daemon_a.datas += Tree('./lib', prefix='lib')
|
||||
daemon_a.datas += Tree('./codec2', prefix='codec2')
|
||||
daemon_a.datas += Tree('lib', prefix='lib')
|
||||
# daemon_a.datas += Tree('./codec2', prefix='codec2')
|
||||
|
||||
|
||||
# TNC --------------------------------------------------
|
||||
|
@ -61,7 +61,7 @@ tnc_exe = EXE(tnc_pyz,
|
|||
tnc_a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='tnc',
|
||||
name='freedata-tnc',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
|
159
tnc/helpers.py
159
tnc/helpers.py
|
@ -13,6 +13,14 @@ import static
|
|||
|
||||
|
||||
def wait(seconds):
|
||||
"""
|
||||
|
||||
Args:
|
||||
seconds:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
timeout = time.time() + seconds
|
||||
|
||||
while time.time() < timeout:
|
||||
|
@ -22,12 +30,17 @@ def wait(seconds):
|
|||
|
||||
|
||||
def get_crc_8(data):
|
||||
"""
|
||||
Author: DJ2LS
|
||||
"""Author: DJ2LS
|
||||
|
||||
Get the CRC8 of a byte string
|
||||
|
||||
param: data = bytes()
|
||||
|
||||
Args:
|
||||
data:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
crc_algorithm = crcengine.new('crc8-ccitt') # load crc8 library
|
||||
crc_data = crc_algorithm(data)
|
||||
|
@ -36,22 +49,57 @@ def get_crc_8(data):
|
|||
|
||||
|
||||
def get_crc_16(data):
|
||||
"""
|
||||
Author: DJ2LS
|
||||
"""Author: DJ2LS
|
||||
|
||||
Get the CRC16 of a byte string
|
||||
|
||||
param: data = bytes()
|
||||
|
||||
Args:
|
||||
data:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc16 library
|
||||
crc_data = crc_algorithm(data)
|
||||
crc_data = crc_data.to_bytes(2, byteorder='big')
|
||||
return crc_data
|
||||
|
||||
def get_crc_32(data):
|
||||
"""Author: DJ2LS
|
||||
|
||||
Get the CRC32 of a byte string
|
||||
|
||||
param: data = bytes()
|
||||
|
||||
Args:
|
||||
data:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
crc_algorithm = crcengine.new('crc32') # load crc16 library
|
||||
crc_data = crc_algorithm(data)
|
||||
crc_data = crc_data.to_bytes(4, byteorder='big')
|
||||
return crc_data
|
||||
|
||||
|
||||
def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
|
||||
"""
|
||||
|
||||
Args:
|
||||
dxcallsign:
|
||||
dxgrid:
|
||||
datatype:
|
||||
snr:
|
||||
offset:
|
||||
frequency:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
# check if buffer empty
|
||||
if len(static.HEARD_STATIONS) == 0:
|
||||
static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency])
|
||||
|
@ -73,31 +121,94 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
|
|||
# item = [dxcallsign, int(time.time())]
|
||||
# static.HEARD_STATIONS[idx] = item
|
||||
|
||||
'''
|
||||
def setup_logging():
|
||||
|
||||
|
||||
def callsign_to_bytes(callsign):
|
||||
"""
|
||||
Author: DJ2LS
|
||||
|
||||
Set the custom logging format so we can use colors
|
||||
Args:
|
||||
callsign:
|
||||
|
||||
# https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output
|
||||
# 'DEBUG' : 37, # white
|
||||
# 'INFO' : 36, # cyan
|
||||
# 'WARNING' : 33, # yellow
|
||||
# 'ERROR' : 31, # red
|
||||
# 'CRITICAL': 41, # white on red bg
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# http://www.aprs.org/aprs11/SSIDs.txt
|
||||
#-0 Your primary station usually fixed and message capable
|
||||
#-1 generic additional station, digi, mobile, wx, etc
|
||||
#-2 generic additional station, digi, mobile, wx, etc
|
||||
#-3 generic additional station, digi, mobile, wx, etc
|
||||
#-4 generic additional station, digi, mobile, wx, etc
|
||||
#-5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)
|
||||
#-6 Special activity, Satellite ops, camping or 6 meters, etc
|
||||
#-7 walkie talkies, HT's or other human portable
|
||||
#-8 boats, sailboats, RV's or second main mobile
|
||||
#-9 Primary Mobile (usually message capable)
|
||||
#-10 internet, Igates, echolink, winlink, AVRS, APRN, etc
|
||||
#-11 balloons, aircraft, spacecraft, etc
|
||||
#-12 APRStt, DTMF, RFID, devices, one-way trackers*, etc
|
||||
#-13 Weather stations
|
||||
#-14 Truckers or generally full time drivers
|
||||
#-15 generic additional station, digi, mobile, wx, etc
|
||||
|
||||
# try converting to bytestring if possible type string
|
||||
try:
|
||||
callsign = bytes(callsign, 'utf-8')
|
||||
except:
|
||||
pass
|
||||
|
||||
# we need to do this step to reduce the needed paypload by the callsign ( stripping "-" out of the callsign )
|
||||
callsign = callsign.split(b'-')
|
||||
try:
|
||||
ssid = int(callsign[1])
|
||||
except:
|
||||
ssid = 0
|
||||
|
||||
callsign = callsign[0]
|
||||
|
||||
bytestring = bytearray(8)
|
||||
bytestring[:len(callsign)] = callsign
|
||||
bytestring[7:8] = bytes([ssid])
|
||||
|
||||
return bytes(bytestring)
|
||||
|
||||
def bytes_to_callsign(bytestring):
|
||||
"""
|
||||
|
||||
Args:
|
||||
bytestring:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
logging.basicConfig(level=logging.INFO,encoding='utf-8',format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s',datefmt='%H:%M:%S',handlers=[logging.FileHandler("FreeDATA-TNC.log"), logging.StreamHandler()])
|
||||
# http://www.aprs.org/aprs11/SSIDs.txt
|
||||
#-0 Your primary station usually fixed and message capable
|
||||
#-1 generic additional station, digi, mobile, wx, etc
|
||||
#-2 generic additional station, digi, mobile, wx, etc
|
||||
#-3 generic additional station, digi, mobile, wx, etc
|
||||
#-4 generic additional station, digi, mobile, wx, etc
|
||||
#-5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)
|
||||
#-6 Special activity, Satellite ops, camping or 6 meters, etc
|
||||
#-7 walkie talkies, HT's or other human portable
|
||||
#-8 boats, sailboats, RV's or second main mobile
|
||||
#-9 Primary Mobile (usually message capable)
|
||||
#-10 internet, Igates, echolink, winlink, AVRS, APRN, etc
|
||||
#-11 balloons, aircraft, spacecraft, etc
|
||||
#-12 APRStt, DTMF, RFID, devices, one-way trackers*, etc
|
||||
#-13 Weather stations
|
||||
#-14 Truckers or generally full time drivers
|
||||
#-15 generic additional station, digi, mobile, wx, etc
|
||||
|
||||
logging.addLevelName(logging.DEBUG, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
|
||||
logging.addLevelName(logging.INFO, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.INFO))
|
||||
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
|
||||
logging.addLevelName(logging.ERROR, "\033[1;31m%s\033[1;0m" % "FAILED")
|
||||
#logging.addLevelName( logging.ERROR, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
|
||||
logging.addLevelName(logging.CRITICAL, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL))
|
||||
# we need to do this step to reduce the needed paypload by the callsign ( stripping "-" out of the callsign )
|
||||
|
||||
callsign = bytes(bytestring[:7])
|
||||
callsign = callsign.rstrip(b'\x00')
|
||||
ssid = int.from_bytes(bytes(bytestring[7:8]), "big")
|
||||
|
||||
callsign = callsign + b'-'
|
||||
callsign = callsign.decode('utf-8')
|
||||
callsign = callsign + str(ssid)
|
||||
callsign = callsign.encode('utf-8')
|
||||
|
||||
return bytes(callsign)
|
||||
|
||||
logging.addLevelName(25, "\033[1;32m%s\033[1;0m" % "SUCCESS")
|
||||
logging.addLevelName(24, "\033[1;34m%s\033[1;0m" % "DATA")
|
||||
'''
|
||||
|
|
BIN
tnc/lib/codec2/linux/armv7l/libcodec2.so.1.0
Normal file
BIN
tnc/lib/codec2/linux/armv7l/libcodec2.so.1.0
Normal file
Binary file not shown.
BIN
tnc/lib/codec2/linux/ia32/libcodec2.so.1.0
Normal file
BIN
tnc/lib/codec2/linux/ia32/libcodec2.so.1.0
Normal file
Binary file not shown.
BIN
tnc/lib/codec2/macOS/libcodec2.1.0.dylib
Normal file
BIN
tnc/lib/codec2/macOS/libcodec2.1.0.dylib
Normal file
Binary file not shown.
BIN
tnc/lib/codec2/windows/libcodec2.dll → tnc/lib/codec2/win32/libcodec2.dll
Normal file → Executable file
BIN
tnc/lib/codec2/windows/libcodec2.dll → tnc/lib/codec2/win32/libcodec2.dll
Normal file → Executable file
Binary file not shown.
BIN
tnc/lib/codec2/win64/libcodec2.dll
Executable file
BIN
tnc/lib/codec2/win64/libcodec2.dll
Executable file
Binary file not shown.
|
@ -1,5 +1,13 @@
|
|||
# https://www.structlog.org/en/stable/standard-library.html
|
||||
def setup_logging(filename):
|
||||
"""
|
||||
|
||||
Args:
|
||||
filename:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
import logging.config
|
||||
import structlog
|
||||
|
|
104
tnc/main.py
Normal file → Executable file
104
tnc/main.py
Normal file → Executable file
|
@ -5,6 +5,9 @@ Created on Tue Dec 22 16:58:45 2020
|
|||
|
||||
@author: DJ2LS
|
||||
|
||||
main module for running the tnc
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
@ -18,38 +21,66 @@ import structlog
|
|||
import log_handler
|
||||
import modem
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
import multiprocessing
|
||||
|
||||
|
||||
# signal handler for closing aplication
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
a signal handler, which closes the network/socket when closing the application
|
||||
Args:
|
||||
sig: signal
|
||||
frame:
|
||||
|
||||
Returns: system exit
|
||||
|
||||
"""
|
||||
print('Closing tnc...')
|
||||
sock.CLOSE_SIGNAL = True
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# we need to run this on windows for multiprocessing support
|
||||
multiprocessing.freeze_support()
|
||||
# --------------------------------------------GET PARAMETER INPUTS
|
||||
PARSER = argparse.ArgumentParser(description='FreeDATA TNC')
|
||||
PARSER.add_argument('--mycall', dest="mycall", default="AA0AA", help="My callsign", type=str)
|
||||
PARSER.add_argument('--mygrid', dest="mygrid", default="JN12AA", help="My gridsquare", type=str)
|
||||
PARSER.add_argument('--rx', dest="audio_input_device", default=0, help="listening sound card", type=int)
|
||||
PARSER.add_argument('--tx', dest="audio_output_device", default=0, help="transmitting sound card", type=int)
|
||||
PARSER.add_argument('--port', dest="socket_port", default=3000, help="Socket port", type=int)
|
||||
PARSER.add_argument('--port', dest="socket_port", default=3000, help="Socket port in the range of 1024-65536", type=int)
|
||||
PARSER.add_argument('--deviceport', dest="hamlib_device_port", default="/dev/ttyUSB0", help="Hamlib device port", type=str)
|
||||
PARSER.add_argument('--devicename', dest="hamlib_device_name", default=2028, help="Hamlib device name", type=str)
|
||||
PARSER.add_argument('--serialspeed', dest="hamlib_serialspeed", default=9600, help="Serialspeed", type=str)
|
||||
PARSER.add_argument('--pttprotocol', dest="hamlib_ptt_type", default='RTS', help="PTT Type", type=str)
|
||||
PARSER.add_argument('--devicename', dest="hamlib_device_name", default="2028", help="Hamlib device name", type=str)
|
||||
PARSER.add_argument('--serialspeed', dest="hamlib_serialspeed", choices=[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200], default=9600, help="Serialspeed", type=int)
|
||||
PARSER.add_argument('--pttprotocol', dest="hamlib_ptt_type", choices=['USB', 'RIG', 'RTS', 'DTR', 'CM108', 'MICDATA', 'PARALLEL', 'DTR-H', 'DTR-L', 'NONE'], default='USB', help="PTT Type", type=str)
|
||||
PARSER.add_argument('--pttport', dest="hamlib_ptt_port", default="/dev/ttyUSB0", help="PTT Port", type=str)
|
||||
PARSER.add_argument('--data_bits', dest="hamlib_data_bits", default="8", help="Hamlib data bits", type=str)
|
||||
PARSER.add_argument('--stop_bits', dest="hamlib_stop_bits", default="1", help="Hamlib stop bits", type=str)
|
||||
PARSER.add_argument('--data_bits', dest="hamlib_data_bits", choices=[7, 8], default=8, help="Hamlib data bits", type=int)
|
||||
PARSER.add_argument('--stop_bits', dest="hamlib_stop_bits", choices=[1, 2], default=1, help="Hamlib stop bits", type=int)
|
||||
PARSER.add_argument('--handshake', dest="hamlib_handshake", default="None", help="Hamlib handshake", type=str)
|
||||
PARSER.add_argument('--radiocontrol', dest="hamlib_radiocontrol", default="direct", help="Set how you want to control your radio")
|
||||
PARSER.add_argument('--rigctld_port', dest="rigctld_port", default="direct", help="Set rigctld port")
|
||||
PARSER.add_argument('--rigctld_ip', dest="rigctld_ip", default="direct", help="Set rigctld ip")
|
||||
|
||||
|
||||
PARSER.add_argument('--radiocontrol', dest="hamlib_radiocontrol", choices=['disabled', 'direct', 'rigctl', 'rigctld'], default="disabled", help="Set how you want to control your radio")
|
||||
PARSER.add_argument('--rigctld_port', dest="rigctld_port", default=4532, type=int, help="Set rigctld port")
|
||||
PARSER.add_argument('--rigctld_ip', dest="rigctld_ip", default="localhost", help="Set rigctld ip")
|
||||
PARSER.add_argument('--scatter', dest="send_scatter", action="store_true", help="Send scatter information via network")
|
||||
PARSER.add_argument('--fft', dest="send_fft", action="store_true", help="Send fft information via network")
|
||||
PARSER.add_argument('--500hz', dest="low_bandwith_mode", action="store_true", help="Enable low bandwith mode ( 500 Hz only )")
|
||||
|
||||
|
||||
ARGS = PARSER.parse_args()
|
||||
|
||||
static.MYCALLSIGN = bytes(ARGS.mycall, 'utf-8')
|
||||
static.MYCALLSIGN_CRC8 = helpers.get_crc_8(static.MYCALLSIGN)
|
||||
static.MYGRID = bytes(ARGS.mygrid, 'utf-8')
|
||||
# additional step for beeing sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
mycallsign = bytes(ARGS.mycall.upper(), 'utf-8')
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||
static.MYCALLSIGN_CRC = helpers.get_crc_16(static.MYCALLSIGN)
|
||||
|
||||
static.MYGRID = bytes(ARGS.mygrid, 'utf-8')
|
||||
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
|
||||
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
||||
static.PORT = ARGS.socket_port
|
||||
|
@ -57,26 +88,52 @@ if __name__ == '__main__':
|
|||
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
|
||||
static.HAMLIB_PTT_TYPE = ARGS.hamlib_ptt_type
|
||||
static.HAMLIB_PTT_PORT = ARGS.hamlib_ptt_port
|
||||
static.HAMLIB_SERIAL_SPEED = ARGS.hamlib_serialspeed
|
||||
static.HAMLIB_DATA_BITS = ARGS.hamlib_data_bits
|
||||
static.HAMLIB_STOP_BITS = ARGS.hamlib_stop_bits
|
||||
static.HAMLIB_SERIAL_SPEED = str(ARGS.hamlib_serialspeed)
|
||||
static.HAMLIB_DATA_BITS = str(ARGS.hamlib_data_bits)
|
||||
static.HAMLIB_STOP_BITS = str(ARGS.hamlib_stop_bits)
|
||||
static.HAMLIB_HANDSHAKE = ARGS.hamlib_handshake
|
||||
static.HAMLIB_RADIOCONTROL = ARGS.hamlib_radiocontrol
|
||||
static.HAMLIB_RGICTLD_IP = ARGS.rigctld_ip
|
||||
static.HAMLIB_RGICTLD_PORT = ARGS.rigctld_port
|
||||
static.HAMLIB_RGICTLD_PORT = str(ARGS.rigctld_port)
|
||||
static.ENABLE_SCATTER = ARGS.send_scatter
|
||||
static.ENABLE_FFT = ARGS.send_fft
|
||||
static.LOW_BANDWITH_MODE = ARGS.low_bandwith_mode
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# we need to wait until we got all parameters from argparse first before we can load the other modules
|
||||
import sock
|
||||
|
||||
# config logging
|
||||
log_handler.setup_logging("tnc")
|
||||
structlog.get_logger("structlog").info("[TNC] Starting FreeDATA", author="DJ2LS", year="2022", version="0.1")
|
||||
try:
|
||||
if sys.platform == 'linux':
|
||||
logging_path = os.getenv("HOME") + '/.config/' + 'FreeDATA/' + 'tnc'
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'tnc'
|
||||
|
||||
if sys.platform == 'win32' or sys.platform == 'win64':
|
||||
logging_path = os.getenv('APPDATA') + '/' + 'FreeDATA/' + 'tnc'
|
||||
|
||||
if not os.path.exists(logging_path):
|
||||
os.makedirs(logging_path)
|
||||
log_handler.setup_logging(logging_path)
|
||||
except:
|
||||
structlog.get_logger("structlog").error("[DMN] logger init error")
|
||||
|
||||
|
||||
|
||||
structlog.get_logger("structlog").info("[TNC] Starting FreeDATA", author="DJ2LS", year="2022", version=static.VERSION)
|
||||
|
||||
# start data handler
|
||||
data_handler.DATA()
|
||||
|
||||
# start modem
|
||||
modem = modem.RF()
|
||||
|
||||
|
||||
# --------------------------------------------START CMD SERVER
|
||||
|
||||
try:
|
||||
|
@ -85,9 +142,12 @@ if __name__ == '__main__':
|
|||
socketserver.TCPServer.allow_reuse_address = True
|
||||
cmdserver = sock.ThreadedTCPServer((static.HOST, static.PORT), sock.ThreadedTCPRequestHandler)
|
||||
server_thread = threading.Thread(target=cmdserver.serve_forever)
|
||||
|
||||
server_thread.daemon = True
|
||||
server_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=e)
|
||||
|
||||
os._exit(1)
|
||||
while 1:
|
||||
time.sleep(1)
|
||||
|
|
278
tnc/modem.py
278
tnc/modem.py
|
@ -6,10 +6,10 @@ Created on Wed Dec 23 07:04:24 2020
|
|||
@author: DJ2LS
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import ctypes
|
||||
from ctypes import *
|
||||
import pathlib
|
||||
#import asyncio
|
||||
import logging, structlog, log_handler
|
||||
import time
|
||||
import threading
|
||||
|
@ -18,46 +18,21 @@ import numpy as np
|
|||
import helpers
|
||||
import static
|
||||
import data_handler
|
||||
|
||||
import ujson as json
|
||||
import sock
|
||||
import re
|
||||
import queue
|
||||
import codec2
|
||||
import audio
|
||||
|
||||
|
||||
####################################################
|
||||
# https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
# we need to have a look at this if we want to run this on Windows and MacOS !
|
||||
# Currently it seems, this is a Linux-only problem
|
||||
|
||||
from ctypes import *
|
||||
from contextlib import contextmanager
|
||||
import pyaudio
|
||||
|
||||
ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)
|
||||
|
||||
def py_error_handler(filename, line, function, err, fmt):
|
||||
pass
|
||||
|
||||
c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
|
||||
|
||||
@contextmanager
|
||||
def noalsaerr():
|
||||
asound = cdll.LoadLibrary('libasound.so')
|
||||
asound.snd_lib_error_set_handler(c_error_handler)
|
||||
yield
|
||||
asound.snd_lib_error_set_handler(None)
|
||||
|
||||
# with noalsaerr():
|
||||
# p = pyaudio.PyAudio()
|
||||
######################################################
|
||||
|
||||
from collections import deque
|
||||
|
||||
MODEM_STATS_NR_MAX = 320
|
||||
MODEM_STATS_NC_MAX = 51
|
||||
|
||||
|
||||
class MODEMSTATS(ctypes.Structure):
|
||||
""" """
|
||||
_fields_ = [
|
||||
("Nc", ctypes.c_int),
|
||||
("snr_est", ctypes.c_float),
|
||||
|
@ -83,8 +58,14 @@ RECEIVE_DATAC1 = False
|
|||
RECEIVE_DATAC3 = False
|
||||
|
||||
class RF():
|
||||
""" """
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.sampler_avg = 0
|
||||
self.buffer_avg = 0
|
||||
|
||||
|
||||
self.AUDIO_SAMPLE_RATE_RX = 48000
|
||||
self.AUDIO_SAMPLE_RATE_TX = 48000
|
||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||
|
@ -93,6 +74,11 @@ class RF():
|
|||
self.AUDIO_CHUNKS = 48 #8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) #48
|
||||
self.AUDIO_CHANNELS = 1
|
||||
|
||||
# locking state for mod out so buffer will be filled before we can use it
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/127
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
||||
self.mod_out_locked = True
|
||||
|
||||
# make sure our resampler will work
|
||||
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
|
||||
|
||||
|
@ -105,13 +91,16 @@ class RF():
|
|||
self.modem_received_queue = MODEM_RECEIVED_QUEUE
|
||||
|
||||
# init FIFO queue to store modulation out in
|
||||
self.modoutqueue = queue.Queue()
|
||||
self.modoutqueue = deque()
|
||||
|
||||
# define fft_data buffer
|
||||
self.fft_data = bytes()
|
||||
|
||||
# open codec2 instance
|
||||
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p)
|
||||
#self.c_lib.freedv_set_tuning_range(self.datac0_freedv, c_float(-150.0), c_float(150.0))
|
||||
|
||||
|
||||
self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv)/8)
|
||||
self.datac0_payload_per_frame = self.datac0_bytes_per_frame -2
|
||||
self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.datac0_freedv)
|
||||
|
@ -143,11 +132,11 @@ class RF():
|
|||
try:
|
||||
# we need to "try" this, because sometimes libasound.so isn't in the default place
|
||||
# try to supress error messages
|
||||
with noalsaerr(): # https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
self.p = pyaudio.PyAudio()
|
||||
with audio.noalsaerr(): # https://github.com/DJ2LS/FreeDATA/issues/22
|
||||
self.p = audio.pyaudio.PyAudio()
|
||||
# else do it the default way
|
||||
except:
|
||||
self.p = pyaudio.PyAudio()
|
||||
self.p = audio.pyaudio.PyAudio()
|
||||
atexit.register(self.p.terminate)
|
||||
|
||||
# --------------------------------------------OPEN RX AUDIO CHANNEL
|
||||
|
@ -162,7 +151,8 @@ class RF():
|
|||
static.AUDIO_OUTPUT_DEVICE = loopback_list[1] #1 = TX
|
||||
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
|
||||
|
||||
self.audio_stream = self.p.open(format=pyaudio.paInt16,
|
||||
try:
|
||||
self.audio_stream = self.p.open(format=audio.pyaudio.paInt16,
|
||||
channels=self.AUDIO_CHANNELS,
|
||||
rate=self.AUDIO_SAMPLE_RATE_RX,
|
||||
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER_RX,
|
||||
|
@ -172,9 +162,16 @@ class RF():
|
|||
output_device_index=static.AUDIO_OUTPUT_DEVICE,
|
||||
stream_callback=self.audio_callback
|
||||
)
|
||||
structlog.get_logger("structlog").info("opened audio devices")
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("can't open audio device. Exit", e=e)
|
||||
os._exit(1)
|
||||
|
||||
try:
|
||||
structlog.get_logger("structlog").debug("[TNC] starting pyaudio callback")
|
||||
self.audio_stream.start_stream()
|
||||
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error("[TNC] starting pyaudio callback failed", e=e)
|
||||
|
||||
|
@ -186,78 +183,112 @@ class RF():
|
|||
import rigctl as rig
|
||||
elif static.HAMLIB_RADIOCONTROL == 'rigctld':
|
||||
import rigctld as rig
|
||||
elif static.HAMLIB_RADIOCONTROL == 'disabled':
|
||||
import rigdummy as rig
|
||||
else:
|
||||
raise NotImplementedError
|
||||
import rigdummy as rig
|
||||
|
||||
|
||||
self.hamlib = rig.radio()
|
||||
self.hamlib.open_rig(devicename=static.HAMLIB_DEVICE_NAME, deviceport=static.HAMLIB_DEVICE_PORT, hamlib_ptt_type=static.HAMLIB_PTT_TYPE, serialspeed=static.HAMLIB_SERIAL_SPEED, pttport=static.HAMLIB_PTT_PORT, data_bits=static.HAMLIB_DATA_BITS, stop_bits=static.HAMLIB_STOP_BITS, handshake=static.HAMLIB_HANDSHAKE, rigctld_ip = static.HAMLIB_RGICTLD_IP, rigctld_port = static.HAMLIB_RGICTLD_PORT)
|
||||
|
||||
# --------------------------------------------START DECODER THREAD
|
||||
|
||||
fft_thread = threading.Thread(target=self.calculate_fft, name="FFT_THREAD")
|
||||
if static.ENABLE_FFT:
|
||||
fft_thread = threading.Thread(target=self.calculate_fft, name="FFT_THREAD" ,daemon=True)
|
||||
fft_thread.start()
|
||||
|
||||
audio_thread_datac0 = threading.Thread(target=self.audio_datac0, name="AUDIO_THREAD DATAC0")
|
||||
audio_thread_datac0 = threading.Thread(target=self.audio_datac0, name="AUDIO_THREAD DATAC0",daemon=True)
|
||||
audio_thread_datac0.start()
|
||||
|
||||
audio_thread_datac1 = threading.Thread(target=self.audio_datac1, name="AUDIO_THREAD DATAC1")
|
||||
audio_thread_datac1 = threading.Thread(target=self.audio_datac1, name="AUDIO_THREAD DATAC1",daemon=True)
|
||||
audio_thread_datac1.start()
|
||||
|
||||
audio_thread_datac3 = threading.Thread(target=self.audio_datac3, name="AUDIO_THREAD DATAC3")
|
||||
audio_thread_datac3 = threading.Thread(target=self.audio_datac3, name="AUDIO_THREAD DATAC3",daemon=True)
|
||||
audio_thread_datac3.start()
|
||||
|
||||
hamlib_thread = threading.Thread(target=self.update_rig_data, name="HAMLIB_THREAD")
|
||||
|
||||
hamlib_thread = threading.Thread(target=self.update_rig_data, name="HAMLIB_THREAD",daemon=True)
|
||||
hamlib_thread.start()
|
||||
|
||||
worker_received = threading.Thread(target=self.worker_received, name="WORKER_THREAD")
|
||||
worker_received = threading.Thread(target=self.worker_received, name="WORKER_THREAD",daemon=True)
|
||||
worker_received.start()
|
||||
|
||||
worker_transmit = threading.Thread(target=self.worker_transmit, name="WORKER_THREAD")
|
||||
worker_transmit = threading.Thread(target=self.worker_transmit, name="WORKER_THREAD",daemon=True)
|
||||
worker_transmit.start()
|
||||
|
||||
# --------------------------------------------------------------------------------------------------------
|
||||
def audio_callback(self, data_in48k, frame_count, time_info, status):
|
||||
"""
|
||||
|
||||
Args:
|
||||
data_in48k:
|
||||
frame_count:
|
||||
time_info:
|
||||
status:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||
x = self.resampler.resample48_to_8(x)
|
||||
|
||||
length_x = len(x)
|
||||
|
||||
# avoid decoding when transmitting to reduce CPU
|
||||
if not static.TRANSMITTING:
|
||||
# avoid buffer overflow by filling only if buffer not full
|
||||
if not self.datac0_buffer.nbuffer+len(x) > self.datac0_buffer.size:
|
||||
if not self.datac0_buffer.nbuffer+length_x > self.datac0_buffer.size:
|
||||
self.datac0_buffer.push(x)
|
||||
else:
|
||||
static.BUFFER_OVERFLOW_COUNTER[0] += 1
|
||||
|
||||
# avoid buffer overflow by filling only if buffer not full and selected datachannel mode
|
||||
if not self.datac1_buffer.nbuffer+len(x) > self.datac1_buffer.size:
|
||||
if not self.datac1_buffer.nbuffer+length_x > self.datac1_buffer.size:
|
||||
if RECEIVE_DATAC1:
|
||||
self.datac1_buffer.push(x)
|
||||
else:
|
||||
static.BUFFER_OVERFLOW_COUNTER[1] += 1
|
||||
|
||||
# avoid buffer overflow by filling only if buffer not full and selected datachannel mode
|
||||
if not self.datac3_buffer.nbuffer+len(x) > self.datac3_buffer.size:
|
||||
if not self.datac3_buffer.nbuffer+length_x > self.datac3_buffer.size:
|
||||
if RECEIVE_DATAC3:
|
||||
self.datac3_buffer.push(x)
|
||||
else:
|
||||
static.BUFFER_OVERFLOW_COUNTER[2] += 1
|
||||
|
||||
if self.modoutqueue.empty():
|
||||
data_out48k = bytes(self.AUDIO_FRAMES_PER_BUFFER_TX*2)
|
||||
if not self.modoutqueue or self.mod_out_locked:
|
||||
data_out48k = np.zeros(frame_count, dtype=np.int16)
|
||||
self.fft_data = bytes(x)
|
||||
|
||||
else:
|
||||
data_out48k = self.modoutqueue.get()
|
||||
data_out48k = self.modoutqueue.popleft()
|
||||
self.fft_data = bytes(data_out48k)
|
||||
|
||||
return (data_out48k, pyaudio.paContinue)
|
||||
return (data_out48k, audio.pyaudio.paContinue)
|
||||
|
||||
# --------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def transmit(self, mode, repeats, repeat_delay, frames):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
repeats:
|
||||
repeat_delay:
|
||||
frames:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
static.TRANSMITTING = True
|
||||
# toggle ptt early to save some time
|
||||
# toggle ptt early to save some time and send ptt state via socket
|
||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
jsondata = {"ptt":"True"}
|
||||
data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(data_out)
|
||||
|
||||
|
||||
# open codec2 instance
|
||||
self.MODE = mode
|
||||
freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
|
||||
|
@ -287,11 +318,8 @@ class RF():
|
|||
for i in range(1,repeats+1):
|
||||
# write preamble to txbuffer
|
||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||
#time.sleep(0.05)
|
||||
threading.Event().wait(0.05)
|
||||
txbuffer += bytes(mod_out_preamble)
|
||||
|
||||
|
||||
# create modulaton for n frames in list
|
||||
for n in range(0,len(frames)):
|
||||
|
||||
|
@ -307,52 +335,58 @@ class RF():
|
|||
|
||||
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
|
||||
#time.sleep(0.05)
|
||||
threading.Event().wait(0.05)
|
||||
txbuffer += bytes(mod_out)
|
||||
|
||||
|
||||
# append postamble to txbuffer
|
||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||
txbuffer += bytes(mod_out_postamble)
|
||||
#time.sleep(0.05)
|
||||
threading.Event().wait(0.05)
|
||||
# add delay to end of frames
|
||||
samples_delay = int(self.MODEM_SAMPLE_RATE*(repeat_delay/1000))
|
||||
mod_out_silence = create_string_buffer(samples_delay*2)
|
||||
txbuffer += bytes(mod_out_silence)
|
||||
#time.sleep(0.05)
|
||||
|
||||
# resample up to 48k (resampler works on np.int16)
|
||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
||||
txbuffer_48k = self.resampler.resample8_to_48(x)
|
||||
|
||||
# split modualted audio to chunks
|
||||
#https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python
|
||||
txbuffer_48k = bytes(txbuffer_48k)
|
||||
chunk = [txbuffer_48k[i:i+self.AUDIO_FRAMES_PER_BUFFER_RX*2] for i in range(0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER_RX*2)]
|
||||
# add modulated chunks to fifo buffer
|
||||
for c in chunk:
|
||||
# if data is shorter than the expcected audio frames per buffer we need to append 0
|
||||
# to prevent the callback from stucking/crashing
|
||||
if len(c) < self.AUDIO_FRAMES_PER_BUFFER_RX*2:
|
||||
delta = bytes(self.AUDIO_FRAMES_PER_BUFFER_RX*2 - len(c))
|
||||
c += delta
|
||||
structlog.get_logger("structlog").debug("[TNC] mod out shorter than audio buffer", delta=len(delta))
|
||||
self.modoutqueue.put(c)
|
||||
# explicitly lock our usage of mod_out_queue if needed
|
||||
# deaktivated for testing purposes
|
||||
self.mod_out_locked = False
|
||||
|
||||
chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX #4800
|
||||
chunk = [txbuffer_48k[i:i+chunk_length] for i in range(0, len(txbuffer_48k), chunk_length)]
|
||||
for c in chunk:
|
||||
if len(c) < chunk_length:
|
||||
delta = chunk_length - len(c)
|
||||
delta_zeros = np.zeros(delta, dtype=np.int16)
|
||||
c = np.append(c, delta_zeros)
|
||||
#structlog.get_logger("structlog").debug("[TNC] mod out shorter than audio buffer", delta=delta)
|
||||
self.modoutqueue.append(c)
|
||||
|
||||
# Release our mod_out_lock so we can use the queue
|
||||
self.mod_out_locked = False
|
||||
|
||||
while self.modoutqueue:
|
||||
time.sleep(0.01)
|
||||
|
||||
# maybe we need to toggle PTT before craeting modulation because of queue processing
|
||||
#static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
while not self.modoutqueue.empty():
|
||||
pass
|
||||
static.PTT_STATE = self.hamlib.set_ptt(False)
|
||||
|
||||
# push ptt state to socket stream
|
||||
jsondata = {"ptt":"False"}
|
||||
data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(data_out)
|
||||
|
||||
# after processing we want to set the locking state back to true to be prepared for next transmission
|
||||
self.mod_out_locked = True
|
||||
|
||||
self.c_lib.freedv_close(freedv)
|
||||
self.modem_transmit_queue.task_done()
|
||||
static.TRANSMITTING = False
|
||||
threading.Event().set()
|
||||
|
||||
def audio_datac0(self):
|
||||
""" """
|
||||
nbytes_datac0 = 0
|
||||
while self.audio_stream.is_active():
|
||||
threading.Event().wait(0.01)
|
||||
|
@ -367,6 +401,7 @@ class RF():
|
|||
self.calculate_snr(self.datac0_freedv)
|
||||
|
||||
def audio_datac1(self):
|
||||
""" """
|
||||
nbytes_datac1 = 0
|
||||
while self.audio_stream.is_active():
|
||||
threading.Event().wait(0.01)
|
||||
|
@ -381,6 +416,7 @@ class RF():
|
|||
self.calculate_snr(self.datac1_freedv)
|
||||
|
||||
def audio_datac3(self):
|
||||
""" """
|
||||
nbytes_datac3 = 0
|
||||
while self.audio_stream.is_active():
|
||||
threading.Event().wait(0.01)
|
||||
|
@ -398,6 +434,7 @@ class RF():
|
|||
|
||||
# worker for FIFO queue for processing received frames
|
||||
def worker_transmit(self):
|
||||
""" """
|
||||
while True:
|
||||
data = self.modem_transmit_queue.get()
|
||||
self.transmit(mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3])
|
||||
|
@ -407,6 +444,7 @@ class RF():
|
|||
|
||||
# worker for FIFO queue for processing received frames
|
||||
def worker_received(self):
|
||||
""" """
|
||||
while True:
|
||||
data = self.modem_received_queue.get()
|
||||
# data[0] = bytes_out
|
||||
|
@ -417,6 +455,14 @@ class RF():
|
|||
|
||||
|
||||
def get_frequency_offset(self, freedv):
|
||||
"""
|
||||
|
||||
Args:
|
||||
freedv:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
modemStats = MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
|
@ -426,6 +472,15 @@ class RF():
|
|||
|
||||
|
||||
def get_scatter(self, freedv):
|
||||
"""
|
||||
|
||||
Args:
|
||||
freedv:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if static.ENABLE_SCATTER:
|
||||
modemStats = MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
|
@ -452,6 +507,14 @@ class RF():
|
|||
|
||||
|
||||
def calculate_snr(self, freedv):
|
||||
"""
|
||||
|
||||
Args:
|
||||
freedv:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
modem_stats_snr = c_float()
|
||||
modem_stats_sync = c_int()
|
||||
|
@ -468,8 +531,9 @@ class RF():
|
|||
return static.SNR
|
||||
|
||||
def update_rig_data(self):
|
||||
""" """
|
||||
while True:
|
||||
#time.sleep(0.5)
|
||||
#time.sleep(1.5)
|
||||
threading.Event().wait(0.5)
|
||||
#(static.HAMLIB_FREQUENCY, static.HAMLIB_MODE, static.HAMLIB_BANDWITH, static.PTT_STATE) = self.hamlib.get_rig_data()
|
||||
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
||||
|
@ -477,6 +541,10 @@ class RF():
|
|||
static.HAMLIB_BANDWITH = self.hamlib.get_bandwith()
|
||||
|
||||
def calculate_fft(self):
|
||||
""" """
|
||||
# channel_busy_delay counter
|
||||
channel_busy_delay = 0
|
||||
|
||||
while True:
|
||||
#time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
|
@ -484,7 +552,8 @@ class RF():
|
|||
if len(self.fft_data) >= 128:
|
||||
|
||||
data_in = self.fft_data
|
||||
# delte fft_buffer
|
||||
|
||||
# delete fft_buffer
|
||||
self.fft_data = bytes()
|
||||
|
||||
# https://gist.github.com/ZWMiller/53232427efc5088007cab6feee7c6e4c
|
||||
|
@ -498,8 +567,35 @@ class RF():
|
|||
# set value 0 to 1 to avoid division by zero
|
||||
fftarray[fftarray == 0] = 1
|
||||
dfft = 10.*np.log10(abs(fftarray))
|
||||
|
||||
# get average of dfft
|
||||
avg = np.mean(dfft)
|
||||
# detect signals which are higher than the average + 10 ( +10 smoothes the output )
|
||||
# data higher than the average must be a signal. Therefore we are setting it to 100 so it will be highlighted
|
||||
# have to do this when we are not transmittig so our own sending data will not affect this too much
|
||||
if not static.TRANSMITTING:
|
||||
dfft[dfft>avg+10] = 100
|
||||
|
||||
# check for signals higher than average by checking for "100"
|
||||
# if we have a signal, increment our channel_busy delay counter so we have a smoother state toggle
|
||||
|
||||
if np.sum(dfft[dfft>avg+10]) >= 300 and not static.TRANSMITTING:
|
||||
static.CHANNEL_BUSY = True
|
||||
channel_busy_delay += 5
|
||||
# limit delay counter to a maximun of 30. The higher this value, the linger we will wait until releasing state
|
||||
if channel_busy_delay > 50:
|
||||
channel_busy_delay = 50
|
||||
else:
|
||||
# decrement channel busy counter if no signal has been detected.
|
||||
channel_busy_delay -= 1
|
||||
if channel_busy_delay < 0:
|
||||
channel_busy_delay = 0
|
||||
# if our channel busy counter reached 0, we toggle state to False
|
||||
if channel_busy_delay == 0:
|
||||
static.CHANNEL_BUSY = False
|
||||
|
||||
# round data to decrease size
|
||||
dfft = np.around(dfft, 1)
|
||||
dfft = np.around(dfft, 0)
|
||||
dfftlist = dfft.tolist()
|
||||
|
||||
static.FFT = dfftlist[0:320] #200 --> bandwith 3000
|
||||
|
@ -508,11 +604,19 @@ class RF():
|
|||
|
||||
structlog.get_logger("structlog").debug("[TNC] Setting fft=0")
|
||||
# else 0
|
||||
static.FFT = [0] * 320
|
||||
static.FFT = [0]
|
||||
else:
|
||||
pass
|
||||
|
||||
def set_frames_per_burst(self, n_frames_per_burst):
|
||||
"""
|
||||
|
||||
Args:
|
||||
n_frames_per_burst:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv,n_frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv,n_frames_per_burst)
|
||||
|
||||
|
@ -520,6 +624,14 @@ class RF():
|
|||
|
||||
|
||||
def get_bytes_per_frame(mode):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
freedv = cast(codec2.api.freedv_open(mode), c_void_p)
|
||||
|
||||
# get number of bytes per frame for mode
|
||||
|
|
47
tnc/rig.py
47
tnc/rig.py
|
@ -63,7 +63,7 @@ except Exception as e:
|
|||
hamlib_version = hamlib_version.split(' ')
|
||||
|
||||
if hamlib_version[1] == 'Hamlib':
|
||||
structlog.get_logger("structlog").warning("[RIG] Rigctl found! Start daemon with parameter --rigctl", version=hamlib_version[2])
|
||||
structlog.get_logger("structlog").warning("[RIG] Rigctl found! Please try using this", version=hamlib_version[2])
|
||||
sys.exit()
|
||||
else:
|
||||
raise Exception
|
||||
|
@ -74,6 +74,7 @@ except Exception as e:
|
|||
|
||||
|
||||
class radio:
|
||||
""" """
|
||||
def __init__(self):
|
||||
|
||||
self.devicename = ''
|
||||
|
@ -88,6 +89,23 @@ class radio:
|
|||
self.handshake = ''
|
||||
|
||||
def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_port, rigctld_ip):
|
||||
"""
|
||||
|
||||
Args:
|
||||
devicename:
|
||||
deviceport:
|
||||
hamlib_ptt_type:
|
||||
serialspeed:
|
||||
pttport:
|
||||
data_bits:
|
||||
stop_bits:
|
||||
handshake:
|
||||
rigctld_port:
|
||||
rigctld_ip:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
self.devicename = devicename
|
||||
self.deviceport = str(deviceport)
|
||||
|
@ -106,7 +124,6 @@ class radio:
|
|||
# get devicenumber by looking for deviceobject in Hamlib module
|
||||
try:
|
||||
self.devicenumber = int(getattr(Hamlib, self.devicename))
|
||||
print(self.devicenumber)
|
||||
except:
|
||||
structlog.get_logger("structlog").error("[RIG] Hamlib: rig not supported...")
|
||||
self.devicenumber = 0
|
||||
|
@ -122,14 +139,6 @@ class radio:
|
|||
self.my_rig.set_conf("ptt_pathname", self.pttport)
|
||||
|
||||
|
||||
print(self.my_rig.get_conf("rig_pathname"))
|
||||
print(self.my_rig.get_conf("retry"))
|
||||
print(self.my_rig.get_conf("serial_speed"))
|
||||
print(self.my_rig.get_conf("serial_handshake"))
|
||||
print(self.my_rig.get_conf("stop_bits"))
|
||||
print(self.my_rig.get_conf("data_bits"))
|
||||
print(self.my_rig.get_conf("ptt_pathname"))
|
||||
|
||||
|
||||
|
||||
if self.hamlib_ptt_type == 'RIG':
|
||||
|
@ -165,9 +174,14 @@ class radio:
|
|||
elif self.hamlib_ptt_type == 'CM108':
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_CM108
|
||||
|
||||
elif self.hamlib_ptt_type == 'RIG_PTT_NONE':
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE
|
||||
|
||||
else: #self.hamlib_ptt_type == 'RIG_PTT_NONE':
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE
|
||||
|
||||
structlog.get_logger("structlog").info("[RIG] Opening...", device=self.devicenumber, path=self.my_rig.get_conf("rig_pathname"), serial_speed=self.my_rig.get_conf("serial_speed"), serial_handshake=self.my_rig.get_conf("serial_handshake"), stop_bits=self.my_rig.get_conf("stop_bits"), data_bits=self.my_rig.get_conf("data_bits"), ptt_pathname=self.my_rig.get_conf("ptt_pathname"))
|
||||
|
||||
|
||||
self.my_rig.open()
|
||||
atexit.register(self.my_rig.close)
|
||||
|
@ -200,13 +214,16 @@ class radio:
|
|||
return False
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
return int(self.my_rig.get_freq())
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
(hamlib_mode, bandwith) = self.my_rig.get_mode()
|
||||
return Hamlib.rig_strrmode(hamlib_mode)
|
||||
|
||||
def get_bandwith(self):
|
||||
""" """
|
||||
(hamlib_mode, bandwith) = self.my_rig.get_mode()
|
||||
return bandwith
|
||||
|
||||
|
@ -215,9 +232,18 @@ class radio:
|
|||
# return 0
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
return self.my_rig.get_ptt()
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if state:
|
||||
self.my_rig.set_ptt(Hamlib.RIG_VFO_CURR, 1)
|
||||
else:
|
||||
|
@ -225,4 +251,5 @@ class radio:
|
|||
return state
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
self.my_rig.close()
|
||||
|
|
|
@ -19,6 +19,7 @@ import os
|
|||
hamlib_version = 0
|
||||
|
||||
class radio:
|
||||
""" """
|
||||
def __init__(self):
|
||||
|
||||
self.devicename = ''
|
||||
|
@ -33,6 +34,23 @@ class radio:
|
|||
self.handshake = ''
|
||||
|
||||
def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port):
|
||||
"""
|
||||
|
||||
Args:
|
||||
devicename:
|
||||
deviceport:
|
||||
hamlib_ptt_type:
|
||||
serialspeed:
|
||||
pttport:
|
||||
data_bits:
|
||||
stop_bits:
|
||||
handshake:
|
||||
rigctld_ip:
|
||||
rigctld_port:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
self.devicename = devicename
|
||||
self.deviceport = deviceport
|
||||
|
@ -87,35 +105,71 @@ class radio:
|
|||
return True
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
cmd = self.cmd + ' f'
|
||||
sw_proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
||||
time.sleep(0.5)
|
||||
freq = sw_proc.communicate()[0]
|
||||
#print('get_frequency', freq, sw_proc.communicate())
|
||||
try:
|
||||
return int(freq)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
#(hamlib_mode, bandwith) = self.my_rig.get_mode()
|
||||
#return Hamlib.rig_strrmode(hamlib_mode)
|
||||
try:
|
||||
return 'PKTUSB'
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def get_bandwith(self):
|
||||
""" """
|
||||
#(hamlib_mode, bandwith) = self.my_rig.get_mode()
|
||||
bandwith = 2700
|
||||
|
||||
try:
|
||||
return bandwith
|
||||
except:
|
||||
return False
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# non usata
|
||||
return 0
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
cmd = self.cmd + ' t'
|
||||
sw_proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
||||
time.sleep(0.5)
|
||||
status = sw_proc.communicate()[0]
|
||||
|
||||
try:
|
||||
return status
|
||||
except:
|
||||
return False
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
cmd = self.cmd + ' T '
|
||||
print('set_ptt', state)
|
||||
if state:
|
||||
|
@ -125,8 +179,12 @@ class radio:
|
|||
print('set_ptt', cmd)
|
||||
|
||||
sw_proc = subprocess.Popen(cmd, shell=True, text=True)
|
||||
try:
|
||||
return state
|
||||
except:
|
||||
return False
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
#self.my_rig.close()
|
||||
return
|
||||
|
|
117
tnc/rigctld.py
117
tnc/rigctld.py
|
@ -1,6 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import structlog
|
||||
import log_handler
|
||||
import logging
|
||||
import time
|
||||
import static
|
||||
# class taken from darsidelemm
|
||||
# rigctl - https://github.com/darksidelemm/rotctld-web-gui/blob/master/rotatorgui.py#L35
|
||||
#
|
||||
|
@ -10,72 +14,145 @@ import logging
|
|||
hamlib_version = 0
|
||||
|
||||
class radio():
|
||||
""" rotctld (hamlib) communication class """
|
||||
"""rotctld (hamlib) communication class"""
|
||||
# Note: This is a massive hack.
|
||||
|
||||
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
|
||||
""" Open a connection to rotctld, and test it for validity """
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(timeout)
|
||||
#self.sock.settimeout(timeout)
|
||||
|
||||
self.connected = False
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
|
||||
self.connection_attempts = 5
|
||||
|
||||
def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port):
|
||||
self.connect()
|
||||
"""
|
||||
|
||||
Args:
|
||||
devicename:
|
||||
deviceport:
|
||||
hamlib_ptt_type:
|
||||
serialspeed:
|
||||
pttport:
|
||||
data_bits:
|
||||
stop_bits:
|
||||
handshake:
|
||||
rigctld_ip:
|
||||
rigctld_port:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.hostname = rigctld_ip
|
||||
self.port = int(rigctld_port)
|
||||
|
||||
|
||||
if self.connect():
|
||||
logging.debug(f"Rigctl intialized")
|
||||
return True
|
||||
else:
|
||||
structlog.get_logger("structlog").error("[RIGCTLD] Can't connect to rigctld!", ip=self.hostname, port=self.port)
|
||||
return False
|
||||
|
||||
def connect(self):
|
||||
""" Connect to rotctld instance """
|
||||
self.sock.connect((self.hostname,self.port))
|
||||
ptt = self.get_ptt()
|
||||
if ptt == None:
|
||||
# Timeout!
|
||||
self.close()
|
||||
raise Exception("Timeout!")
|
||||
else:
|
||||
return ptt
|
||||
|
||||
"""Connect to rigctld instance"""
|
||||
if not self.connected:
|
||||
try:
|
||||
self.connection = socket.create_connection((self.hostname,self.port))
|
||||
self.connected = True
|
||||
structlog.get_logger("structlog").info("[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port)
|
||||
return True
|
||||
except Exception as e:
|
||||
# ConnectionRefusedError: [Errno 111] Connection refused
|
||||
self.close_rig()
|
||||
structlog.get_logger("structlog").warning("[RIGCTLD] Connection to rigctld refused! Reconnect...", ip=self.hostname, port=self.port, e=e)
|
||||
return False
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
self.sock.close()
|
||||
self.connected = False
|
||||
|
||||
|
||||
def send_command(self, command):
|
||||
""" Send a command to the connected rotctld instance,
|
||||
"""Send a command to the connected rotctld instance,
|
||||
and return the return value.
|
||||
|
||||
Args:
|
||||
command:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.sock.sendall(command+b'\n')
|
||||
if self.connected:
|
||||
try:
|
||||
return self.sock.recv(1024)
|
||||
self.connection.sendall(command+b'\n')
|
||||
except:
|
||||
return None
|
||||
structlog.get_logger("structlog").warning("[RIGCTLD] Command not executed!", command=command, ip=self.hostname, port=self.port)
|
||||
self.connected = False
|
||||
|
||||
try:
|
||||
return self.connection.recv(1024)
|
||||
except:
|
||||
structlog.get_logger("structlog").warning("[RIGCTLD] No command response!", command=command, ip=self.hostname, port=self.port)
|
||||
self.connected = False
|
||||
else:
|
||||
|
||||
# reconnecting....
|
||||
time.sleep(0.5)
|
||||
self.connect()
|
||||
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = data.split(b'\n')
|
||||
mode = data[0]
|
||||
return mode.decode("utf-8")
|
||||
|
||||
except:
|
||||
0
|
||||
def get_bandwith(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = data.split(b'\n')
|
||||
bandwith = data[1]
|
||||
return bandwith.decode("utf-8")
|
||||
except:
|
||||
return 0
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
try:
|
||||
frequency = self.send_command(b"f")
|
||||
return frequency.decode("utf-8")
|
||||
except:
|
||||
return 0
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
try:
|
||||
return self.send_command(b"t")
|
||||
except:
|
||||
return False
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
if state:
|
||||
self.send_command(b"T 1")
|
||||
else:
|
||||
self.send_command(b"T 0")
|
||||
return state
|
||||
|
||||
except:
|
||||
return False
|
||||
|
|
65
tnc/rigdummy.py
Normal file
65
tnc/rigdummy.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import structlog
|
||||
|
||||
hamlib_version = 0
|
||||
|
||||
class radio:
|
||||
""" """
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
def open_rig(self, **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def get_bandwith(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return state
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
return
|
||||
|
674
tnc/sock.py
674
tnc/sock.py
|
@ -18,9 +18,7 @@ Created on Fri Dec 25 21:25:14 2020
|
|||
# "dxcallsign" : "..."
|
||||
# "data" : "..."
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import socketserver
|
||||
import threading
|
||||
import ujson as json
|
||||
|
@ -31,252 +29,566 @@ import helpers
|
|||
import sys
|
||||
import os
|
||||
import logging, structlog, log_handler
|
||||
import queue
|
||||
import psutil
|
||||
import audio
|
||||
import base64
|
||||
import atexit
|
||||
|
||||
SOCKET_QUEUE = queue.Queue()
|
||||
DAEMON_QUEUE = queue.Queue()
|
||||
|
||||
CONNECTED_CLIENTS = set()
|
||||
CLOSE_SIGNAL = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
"""
|
||||
the socket handler base class
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
|
||||
def handle(self):
|
||||
|
||||
structlog.get_logger("structlog").debug("[TNC] Client connected", ip=self.client_address[0], port=self.client_address[1])
|
||||
class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||
""" """
|
||||
|
||||
# set encoding
|
||||
encoding = 'utf-8'
|
||||
|
||||
# loop through socket buffer until timeout is reached. then close buffer
|
||||
socketTimeout = time.time() + static.SOCKET_TIMEOUT
|
||||
while socketTimeout > time.time():
|
||||
def send_to_client(self):
|
||||
"""
|
||||
function called by socket handler
|
||||
send data to a network client if available
|
||||
|
||||
time.sleep(0.01)
|
||||
"""
|
||||
tempdata = b''
|
||||
while self.connection_alive and not CLOSE_SIGNAL:
|
||||
|
||||
# send tnc state as network stream
|
||||
# check server port against daemon port and send corresponding data
|
||||
|
||||
if self.server.server_address[1] == static.PORT and not static.TNCSTARTED:
|
||||
data = send_tnc_state()
|
||||
if data != tempdata:
|
||||
tempdata = data
|
||||
SOCKET_QUEUE.put(data)
|
||||
else:
|
||||
data = send_daemon_state()
|
||||
if data != tempdata:
|
||||
tempdata = data
|
||||
SOCKET_QUEUE.put(data)
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
while not SOCKET_QUEUE.empty():
|
||||
data = SOCKET_QUEUE.get()
|
||||
sock_data = bytes(data, 'utf-8')
|
||||
sock_data += b'\n' # append line limiter
|
||||
|
||||
# send data to all clients
|
||||
#try:
|
||||
for client in CONNECTED_CLIENTS:
|
||||
try:
|
||||
client.send(sock_data)
|
||||
except Exception as e:
|
||||
print("connection lost...")
|
||||
print(e)
|
||||
self.connection_alive = False
|
||||
|
||||
# we want to transmit scatter data only once to reduce network traffic
|
||||
static.SCATTER = []
|
||||
# we want to display INFO messages only once
|
||||
static.INFO = []
|
||||
#self.request.sendall(sock_data)
|
||||
time.sleep(0.15)
|
||||
|
||||
def receive_from_client(self):
|
||||
"""
|
||||
function which is called by the socket handler
|
||||
it processes the data which is returned by a client
|
||||
"""
|
||||
data = bytes()
|
||||
while self.connection_alive and not CLOSE_SIGNAL:
|
||||
try:
|
||||
chunk = self.request.recv(1024)
|
||||
data += chunk
|
||||
|
||||
# we need to loop through buffer until end of chunk is reached or timeout occured
|
||||
while socketTimeout > time.time():
|
||||
|
||||
data += self.request.recv(64) # we keep amount of bytes short
|
||||
|
||||
if data.startswith(b'{"type"') and data.endswith(b'}\n'):
|
||||
break
|
||||
|
||||
data = data[:-1] # remove b'\n'
|
||||
data = str(data, encoding)
|
||||
|
||||
if len(data) > 0:
|
||||
# reset socket timeout
|
||||
socketTimeout = time.time() + static.SOCKET_TIMEOUT
|
||||
# only read first line of string. multiple lines will cause an json error
|
||||
# this occurs possibly, if we are getting data too fast
|
||||
# data = data.splitlines()[0]
|
||||
data = data.splitlines()[0]
|
||||
print(data)
|
||||
if chunk == b'':
|
||||
#print("connection broken. Closing...")
|
||||
self.connection_alive = False
|
||||
|
||||
|
||||
if data.startswith(b'{') and data.endswith(b'}\n'):
|
||||
# split data by \n if we have multiple commands in socket buffer
|
||||
data = data.split(b'\n')
|
||||
# remove empty data
|
||||
data.remove(b'')
|
||||
|
||||
# iterate thorugh data list
|
||||
for commands in data:
|
||||
if self.server.server_address[1] == static.PORT:
|
||||
process_tnc_commands(commands)
|
||||
else:
|
||||
process_daemon_commands(commands)
|
||||
|
||||
# wait some time between processing multiple commands
|
||||
# this is only a first test to avoid doubled transmission
|
||||
# we might improve this by only processing one command or
|
||||
# doing some kind of selection to determin which commands need to be dropped
|
||||
# and which one can be processed during a running transmission
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
# finally delete our rx buffer to be ready for new commands
|
||||
data = bytes()
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").info("[SCK] Connection closed", ip=self.client_address[0], port=self.client_address[1], e=e)
|
||||
self.connection_alive = False
|
||||
|
||||
|
||||
def handle(self):
|
||||
"""
|
||||
socket handler
|
||||
"""
|
||||
|
||||
CONNECTED_CLIENTS.add(self.request)
|
||||
|
||||
structlog.get_logger("structlog").debug("[SCK] Client connected", ip=self.client_address[0], port=self.client_address[1])
|
||||
self.connection_alive = True
|
||||
|
||||
self.sendThread = threading.Thread(target=self.send_to_client, args=[],daemon=True).start()
|
||||
self.receiveThread = threading.Thread(target=self.receive_from_client, args=[],daemon=True).start()
|
||||
|
||||
# keep connection alive until we close it
|
||||
while self.connection_alive and not CLOSE_SIGNAL:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
def finish(self):
|
||||
""" """
|
||||
structlog.get_logger("structlog").warning("[SCK] Closing client socket", ip=self.client_address[0], port=self.client_address[1])
|
||||
try:
|
||||
CONNECTED_CLIENTS.remove(self.request)
|
||||
except:
|
||||
print("client connection already removed from client list")
|
||||
|
||||
|
||||
def process_tnc_commands(data):
|
||||
"""
|
||||
process tnc commands
|
||||
|
||||
Args:
|
||||
data:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# we need to do some error handling in case of socket timeout or decoding issue
|
||||
try:
|
||||
|
||||
# convert data to json object
|
||||
received_json = json.loads(data)
|
||||
|
||||
# CQ CQ CQ -----------------------------------------------------
|
||||
if received_json["command"] == "CQCQCQ":
|
||||
if received_json["command"] == "cqcqcq":
|
||||
try:
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['CQ'])
|
||||
command_response("cqcqcq", True)
|
||||
|
||||
except Exception as e:
|
||||
command_response("cqcqcq", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
# START_BEACON -----------------------------------------------------
|
||||
if received_json["command"] == "START_BEACON":
|
||||
|
||||
if received_json["command"] == "start_beacon":
|
||||
try:
|
||||
static.BEACON_STATE = True
|
||||
interval = int(received_json["parameter"])
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['BEACON', interval, True])
|
||||
|
||||
command_response("start_beacon", True)
|
||||
except Exception as e:
|
||||
command_response("start_beacon", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
# STOP_BEACON -----------------------------------------------------
|
||||
if received_json["command"] == "STOP_BEACON":
|
||||
static.BEACON_STATE = False
|
||||
if received_json["command"] == "stop_beacon":
|
||||
try:
|
||||
structlog.get_logger("structlog").warning("[TNC] Stopping beacon!")
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['BEACON', interval, False])
|
||||
|
||||
static.BEACON_STATE = False
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['BEACON', None, False])
|
||||
command_response("stop_beacon", True)
|
||||
except Exception as e:
|
||||
command_response("stop_beacon", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
# PING ----------------------------------------------------------
|
||||
if received_json["type"] == 'PING' and received_json["command"] == "PING":
|
||||
if received_json["type"] == 'ping' and received_json["command"] == "ping":
|
||||
# send ping frame and wait for ACK
|
||||
try:
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
|
||||
# additional step for beeing sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['PING', dxcallsign])
|
||||
command_response("ping", True)
|
||||
except Exception as e:
|
||||
command_response("ping", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
|
||||
|
||||
if received_json["type"] == 'ARQ' and received_json["command"] == "sendFile":
|
||||
static.TNC_STATE = 'BUSY'
|
||||
|
||||
# on a new transmission we reset the timer
|
||||
static.ARQ_START_OF_TRANSMISSION = int(time.time())
|
||||
|
||||
# CONNECT ----------------------------------------------------------
|
||||
if received_json["type"] == 'arq' and received_json["command"] == "connect":
|
||||
static.BEACON_PAUSE = True
|
||||
# send ping frame and wait for ACK
|
||||
try:
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
mode = int(received_json["mode"])
|
||||
n_frames = int(received_json["n_frames"])
|
||||
filename = received_json["filename"]
|
||||
filetype = received_json["filetype"]
|
||||
data = received_json["data"]
|
||||
checksum = received_json["checksum"]
|
||||
|
||||
# additional step for beeing sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||
|
||||
static.DXCALLSIGN = bytes(dxcallsign, 'utf-8')
|
||||
static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN)
|
||||
static.DXCALLSIGN = dxcallsign
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_16(static.DXCALLSIGN)
|
||||
|
||||
# dt = datatype
|
||||
# --> f = file
|
||||
# --> m = message
|
||||
# fn = filename
|
||||
# ft = filetype
|
||||
# d = data
|
||||
# crc = checksum
|
||||
rawdata = {"dt": "f", "fn": filename, "ft": filetype,"d": data, "crc": checksum}
|
||||
dataframe = json.dumps(rawdata)
|
||||
data_out = bytes(dataframe, 'utf-8')
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_FILE', data_out, mode, n_frames])
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['CONNECT', dxcallsign])
|
||||
command_response("connect", True)
|
||||
except Exception as e:
|
||||
command_response("connect", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
# DISCONNECT ----------------------------------------------------------
|
||||
if received_json["type"] == 'arq' and received_json["command"] == "disconnect":
|
||||
# send ping frame and wait for ACK
|
||||
try:
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['DISCONNECT'])
|
||||
command_response("disconnect", True)
|
||||
except Exception as e:
|
||||
command_response("disconnect", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
# TRANSMIT RAW DATA -------------------------------------------
|
||||
if received_json["type"] == 'arq' and received_json["command"] == "send_raw":
|
||||
|
||||
# send message
|
||||
if received_json["type"] == 'ARQ' and received_json["command"] == "sendMessage":
|
||||
static.TNC_STATE = 'BUSY'
|
||||
print(received_json)
|
||||
# on a new transmission we reset the timer
|
||||
static.ARQ_START_OF_TRANSMISSION = int(time.time())
|
||||
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
mode = int(received_json["mode"])
|
||||
n_frames = int(received_json["n_frames"])
|
||||
data = received_json["data"] # d = data
|
||||
checksum = received_json["checksum"] # crc = checksum
|
||||
|
||||
|
||||
static.DXCALLSIGN = bytes(dxcallsign, 'utf-8')
|
||||
static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN)
|
||||
static.BEACON_PAUSE = True
|
||||
try:
|
||||
if not static.ARQ_SESSION:
|
||||
dxcallsign = received_json["parameter"][0]["dxcallsign"]
|
||||
# additional step for beeing sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||
static.DXCALLSIGN = dxcallsign
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_16(static.DXCALLSIGN)
|
||||
command_response("send_raw", True)
|
||||
else:
|
||||
dxcallsign = static.DXCALLSIGN
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_16(static.DXCALLSIGN)
|
||||
|
||||
# dt = datatype
|
||||
# --> f = file
|
||||
# --> m = message
|
||||
# fn = filename
|
||||
# ft = filetype
|
||||
# d = data
|
||||
# crc = checksum
|
||||
rawdata = {"dt": "m","d": data, "crc": checksum}
|
||||
dataframe = json.dumps(rawdata)
|
||||
data_out = bytes(dataframe, 'utf-8')
|
||||
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_MESSAGE', data_out, mode, n_frames])
|
||||
mode = int(received_json["parameter"][0]["mode"])
|
||||
n_frames = int(received_json["parameter"][0]["n_frames"])
|
||||
base64data = received_json["parameter"][0]["data"]
|
||||
|
||||
if not len(base64data) % 4:
|
||||
binarydata = base64.b64decode(base64data)
|
||||
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', binarydata, mode, n_frames])
|
||||
|
||||
else:
|
||||
raise TypeError
|
||||
except Exception as e:
|
||||
command_response("send_raw", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
|
||||
|
||||
if received_json["type"] == 'ARQ' and received_json["command"] == "stopTransmission":
|
||||
# STOP TRANSMISSION ----------------------------------------------------------
|
||||
if received_json["type"] == 'arq' and received_json["command"] == "stop_transmission":
|
||||
try:
|
||||
if static.TNC_STATE == 'BUSY' or static.ARQ_STATE:
|
||||
data_handler.DATA_QUEUE_TRANSMIT.put(['STOP'])
|
||||
print(" >>> STOPPING TRANSMISSION <<<")
|
||||
structlog.get_logger("structlog").warning("[TNC] Stopping transmission!")
|
||||
static.TNC_STATE = 'IDLE'
|
||||
static.ARQ_STATE = False
|
||||
|
||||
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'STATION_INFO':
|
||||
output = {
|
||||
"COMMAND": "STATION_INFO",
|
||||
"TIMESTAMP": received_json["timestamp"],
|
||||
"MY_CALLSIGN": str(static.MYCALLSIGN, encoding),
|
||||
"DX_CALLSIGN": str(static.DXCALLSIGN, encoding),
|
||||
"DX_GRID": str(static.DXGRID, encoding),
|
||||
"EOF": "EOF",
|
||||
}
|
||||
|
||||
jsondata = json.dumps(output)
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'TNC_STATE':
|
||||
|
||||
output = {
|
||||
"COMMAND": "TNC_STATE",
|
||||
"TIMESTAMP": received_json["timestamp"],
|
||||
"PTT_STATE": str(static.PTT_STATE),
|
||||
#"CHANNEL_STATE": str(static.CHANNEL_STATE),
|
||||
"TNC_STATE": str(static.TNC_STATE),
|
||||
"ARQ_STATE": str(static.ARQ_STATE),
|
||||
"AUDIO_RMS": str(static.AUDIO_RMS),
|
||||
"SNR": str(static.SNR),
|
||||
"FREQUENCY": str(static.HAMLIB_FREQUENCY),
|
||||
"MODE": str(static.HAMLIB_MODE),
|
||||
"BANDWITH": str(static.HAMLIB_BANDWITH),
|
||||
"FFT": str(static.FFT),
|
||||
"SCATTER": static.SCATTER,
|
||||
"RX_BUFFER_LENGTH": str(len(static.RX_BUFFER)),
|
||||
"RX_MSG_BUFFER_LENGTH": str(len(static.RX_MSG_BUFFER)),
|
||||
"ARQ_BYTES_PER_MINUTE": str(static.ARQ_BYTES_PER_MINUTE),
|
||||
"ARQ_BYTES_PER_MINUTE_BURST": str(static.ARQ_BYTES_PER_MINUTE_BURST),
|
||||
"ARQ_COMPRESSION_FACTOR": str(static.ARQ_COMPRESSION_FACTOR),
|
||||
"ARQ_TRANSMISSION_PERCENT": str(static.ARQ_TRANSMISSION_PERCENT),
|
||||
"TOTAL_BYTES": str(static.TOTAL_BYTES),
|
||||
"INFO" : static.INFO,
|
||||
"BEACON_STATE" : str(static.BEACON_STATE),
|
||||
"STATIONS": [],
|
||||
"EOF": "EOF",
|
||||
}
|
||||
|
||||
# we want to transmit scatter data only once to reduce network traffic
|
||||
static.SCATTER = []
|
||||
|
||||
# we want to display INFO messages only once
|
||||
static.INFO = []
|
||||
|
||||
# add heard stations to heard stations object
|
||||
for i in range(0, len(static.HEARD_STATIONS)):
|
||||
output["STATIONS"].append({"DXCALLSIGN": str(static.HEARD_STATIONS[i][0], 'utf-8'), "DXGRID": str(static.HEARD_STATIONS[i][1], 'utf-8'),"TIMESTAMP": static.HEARD_STATIONS[i][2], "DATATYPE": static.HEARD_STATIONS[i][3], "SNR": static.HEARD_STATIONS[i][4], "OFFSET": static.HEARD_STATIONS[i][5], "FREQUENCY": static.HEARD_STATIONS[i][6]})
|
||||
|
||||
try:
|
||||
jsondata = json.dumps(output)
|
||||
except ValueError as e:
|
||||
structlog.get_logger("structlog").error(e, data=jsondata)
|
||||
|
||||
try:
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
command_response("stop_transmission", True)
|
||||
except Exception as e:
|
||||
structlog.get_logger("structlog").error(e, data=jsondata)
|
||||
command_response("stop_transmission", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'RX_BUFFER':
|
||||
|
||||
if received_json["type"] == 'get' and received_json["command"] == 'rx_buffer':
|
||||
try:
|
||||
output = {
|
||||
"COMMAND": "RX_BUFFER",
|
||||
"DATA-ARRAY": [],
|
||||
"EOF": "EOF",
|
||||
"command": "rx_buffer",
|
||||
"data-array": [],
|
||||
}
|
||||
|
||||
for i in range(0, len(static.RX_BUFFER)):
|
||||
|
||||
rawdata = json.loads(static.RX_BUFFER[i][3])
|
||||
output["DATA-ARRAY"].append({"DXCALLSIGN": str(static.RX_BUFFER[i][0], 'utf-8'), "DXGRID": str(static.RX_BUFFER[i][1], 'utf-8'), "TIMESTAMP": static.RX_BUFFER[i][2], "RXDATA": [rawdata]})
|
||||
#print(static.RX_BUFFER[i][4])
|
||||
#rawdata = json.loads(static.RX_BUFFER[i][4])
|
||||
base64_data = static.RX_BUFFER[i][4]
|
||||
output["data-array"].append({"uuid": static.RX_BUFFER[i][0],"timestamp": static.RX_BUFFER[i][1], "dxcallsign": str(static.RX_BUFFER[i][2], 'utf-8'), "dxgrid": str(static.RX_BUFFER[i][3], 'utf-8'), "data": base64_data})
|
||||
|
||||
jsondata = json.dumps(output)
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
#self.request.sendall(bytes(jsondata, encoding))
|
||||
SOCKET_QUEUE.put(jsondata)
|
||||
command_response("rx_buffer", True)
|
||||
|
||||
if received_json["type"] == 'GET' and received_json["command"] == 'RX_MSG_BUFFER':
|
||||
output = {
|
||||
"COMMAND": "RX_MSG_BUFFER",
|
||||
"DATA-ARRAY": [],
|
||||
"EOF": "EOF",
|
||||
}
|
||||
for i in range(0, len(static.RX_MSG_BUFFER)):
|
||||
except Exception as e:
|
||||
command_response("rx_buffer", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
rawdata = json.loads(static.RX_MSG_BUFFER[i][3])
|
||||
output["DATA-ARRAY"].append({"DXCALLSIGN": str(static.RX_MSG_BUFFER[i][0], 'utf-8'), "DXGRID": str(static.RX_MSG_BUFFER[i][1], 'utf-8'), "TIMESTAMP": static.RX_MSG_BUFFER[i][2], "RXDATA": [rawdata]})
|
||||
|
||||
jsondata = json.dumps(output)
|
||||
self.request.sendall(bytes(jsondata, encoding))
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'DEL_RX_BUFFER':
|
||||
if received_json["type"] == 'set' and received_json["command"] == 'del_rx_buffer':
|
||||
try:
|
||||
static.RX_BUFFER = []
|
||||
|
||||
if received_json["type"] == 'SET' and received_json["command"] == 'DEL_RX_MSG_BUFFER':
|
||||
static.RX_MSG_BUFFER = []
|
||||
command_response("del_rx_buffer", True)
|
||||
except Exception as e:
|
||||
command_response("del_rx_buffer", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
# exception, if JSON cant be decoded
|
||||
except Exception as e:
|
||||
#socketTimeout = 0
|
||||
structlog.get_logger("structlog").error("[TNC] Network error", e=e)
|
||||
structlog.get_logger("structlog").warning("[TNC] Closing client socket", ip=self.client_address[0], port=self.client_address[1])
|
||||
structlog.get_logger("structlog").error("[TNC] JSON decoding error", e=e)
|
||||
|
||||
def send_tnc_state():
|
||||
"""
|
||||
send the tnc state to network
|
||||
"""
|
||||
|
||||
encoding = 'utf-8'
|
||||
|
||||
output = {
|
||||
"command": "tnc_state",
|
||||
"ptt_state": str(static.PTT_STATE),
|
||||
"tnc_state": str(static.TNC_STATE),
|
||||
"arq_state": str(static.ARQ_STATE),
|
||||
"arq_session": str(static.ARQ_SESSION),
|
||||
"arq_session_state": str(static.ARQ_SESSION_STATE),
|
||||
"audio_rms": str(static.AUDIO_RMS),
|
||||
"snr": str(static.SNR),
|
||||
"frequency": str(static.HAMLIB_FREQUENCY),
|
||||
"speed_level": str(static.ARQ_SPEED_LEVEL),
|
||||
"mode": str(static.HAMLIB_MODE),
|
||||
"bandwith": str(static.HAMLIB_BANDWITH),
|
||||
"fft": str(static.FFT),
|
||||
"channel_busy": str(static.CHANNEL_BUSY),
|
||||
"scatter": static.SCATTER,
|
||||
"rx_buffer_length": str(len(static.RX_BUFFER)),
|
||||
"rx_msg_buffer_length": str(len(static.RX_MSG_BUFFER)),
|
||||
"arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE),
|
||||
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
|
||||
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
|
||||
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
|
||||
"total_bytes": str(static.TOTAL_BYTES),
|
||||
"info" : static.INFO,
|
||||
"beacon_state" : str(static.BEACON_STATE),
|
||||
"stations": [],
|
||||
"mycallsign": str(static.MYCALLSIGN, encoding),
|
||||
"dxcallsign": str(static.DXCALLSIGN, encoding),
|
||||
"dxgrid": str(static.DXGRID, encoding),
|
||||
}
|
||||
|
||||
# add heard stations to heard stations object
|
||||
for i in range(0, len(static.HEARD_STATIONS)):
|
||||
output["stations"].append({"dxcallsign": str(static.HEARD_STATIONS[i][0], 'utf-8'), "dxgrid": str(static.HEARD_STATIONS[i][1], 'utf-8'),"timestamp": static.HEARD_STATIONS[i][2], "datatype": static.HEARD_STATIONS[i][3], "snr": static.HEARD_STATIONS[i][4], "offset": static.HEARD_STATIONS[i][5], "frequency": static.HEARD_STATIONS[i][6]})
|
||||
|
||||
jsondata = json.dumps(output)
|
||||
return jsondata
|
||||
|
||||
|
||||
def process_daemon_commands(data):
|
||||
"""
|
||||
process daemon commands
|
||||
|
||||
Args:
|
||||
data:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# convert data to json object
|
||||
received_json = json.loads(data)
|
||||
|
||||
if received_json["type"] == 'set' and received_json["command"] == 'mycallsign':
|
||||
try:
|
||||
callsign = received_json["parameter"]
|
||||
|
||||
if bytes(callsign, 'utf-8') == b'':
|
||||
self.request.sendall(b'INVALID CALLSIGN')
|
||||
structlog.get_logger("structlog").warning("[DMN] SET MYCALL FAILED", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC)
|
||||
else:
|
||||
static.MYCALLSIGN = bytes(callsign, 'utf-8')
|
||||
static.MYCALLSIGN_CRC = helpers.get_crc_16(static.MYCALLSIGN)
|
||||
|
||||
command_response("mycallsign", True)
|
||||
structlog.get_logger("structlog").info("[DMN] SET MYCALL", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC)
|
||||
except Exception as e:
|
||||
command_response("mycallsign", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
|
||||
if received_json["type"] == 'set' and received_json["command"] == 'mygrid':
|
||||
try:
|
||||
mygrid = received_json["parameter"]
|
||||
|
||||
if bytes(mygrid, 'utf-8') == b'':
|
||||
self.request.sendall(b'INVALID GRID')
|
||||
else:
|
||||
static.MYGRID = bytes(mygrid, 'utf-8')
|
||||
structlog.get_logger("structlog").info("[SCK] SET MYGRID", grid=static.MYGRID)
|
||||
command_response("mygrid", True)
|
||||
except Exception as e:
|
||||
command_response("mygrid", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
|
||||
if received_json["type"] == 'set' and received_json["command"] == 'start_tnc' and not static.TNCSTARTED:
|
||||
|
||||
try:
|
||||
mycall = str(received_json["parameter"][0]["mycall"])
|
||||
mygrid = str(received_json["parameter"][0]["mygrid"])
|
||||
rx_audio = str(received_json["parameter"][0]["rx_audio"])
|
||||
tx_audio = str(received_json["parameter"][0]["tx_audio"])
|
||||
devicename = str(received_json["parameter"][0]["devicename"])
|
||||
deviceport = str(received_json["parameter"][0]["deviceport"])
|
||||
serialspeed = str(received_json["parameter"][0]["serialspeed"])
|
||||
pttprotocol = str(received_json["parameter"][0]["pttprotocol"])
|
||||
pttport = str(received_json["parameter"][0]["pttport"])
|
||||
data_bits = str(received_json["parameter"][0]["data_bits"])
|
||||
stop_bits = str(received_json["parameter"][0]["stop_bits"])
|
||||
handshake = str(received_json["parameter"][0]["handshake"])
|
||||
radiocontrol = str(received_json["parameter"][0]["radiocontrol"])
|
||||
rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"])
|
||||
rigctld_port = str(received_json["parameter"][0]["rigctld_port"])
|
||||
enable_scatter = str(received_json["parameter"][0]["enable_scatter"])
|
||||
enable_fft = str(received_json["parameter"][0]["enable_fft"])
|
||||
low_bandwith_mode = str(received_json["parameter"][0]["low_bandwith_mode"])
|
||||
|
||||
DAEMON_QUEUE.put(['STARTTNC', \
|
||||
mycall, \
|
||||
mygrid, \
|
||||
rx_audio, \
|
||||
tx_audio, \
|
||||
devicename, \
|
||||
deviceport, \
|
||||
serialspeed, \
|
||||
pttprotocol, \
|
||||
pttport, \
|
||||
data_bits, \
|
||||
stop_bits, \
|
||||
handshake, \
|
||||
radiocontrol, \
|
||||
rigctld_ip, \
|
||||
rigctld_port, \
|
||||
enable_scatter, \
|
||||
enable_fft, \
|
||||
low_bandwith_mode \
|
||||
])
|
||||
command_response("start_tnc", True)
|
||||
|
||||
except Exception as e:
|
||||
command_response("start_tnc", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
if received_json["type"] == 'get' and received_json["command"] == 'test_hamlib':
|
||||
|
||||
try:
|
||||
devicename = str(received_json["parameter"][0]["devicename"])
|
||||
deviceport = str(received_json["parameter"][0]["deviceport"])
|
||||
serialspeed = str(received_json["parameter"][0]["serialspeed"])
|
||||
pttprotocol = str(received_json["parameter"][0]["pttprotocol"])
|
||||
pttport = str(received_json["parameter"][0]["pttport"])
|
||||
data_bits = str(received_json["parameter"][0]["data_bits"])
|
||||
stop_bits = str(received_json["parameter"][0]["stop_bits"])
|
||||
handshake = str(received_json["parameter"][0]["handshake"])
|
||||
radiocontrol = str(received_json["parameter"][0]["radiocontrol"])
|
||||
rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"])
|
||||
rigctld_port = str(received_json["parameter"][0]["rigctld_port"])
|
||||
|
||||
DAEMON_QUEUE.put(['TEST_HAMLIB', \
|
||||
devicename, \
|
||||
deviceport, \
|
||||
serialspeed, \
|
||||
pttprotocol, \
|
||||
pttport, \
|
||||
data_bits, \
|
||||
stop_bits, \
|
||||
handshake, \
|
||||
radiocontrol, \
|
||||
rigctld_ip, \
|
||||
rigctld_port \
|
||||
])
|
||||
command_response("test_hamlib", True)
|
||||
except Exception as e:
|
||||
command_response("test_hamlib", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
if received_json["type"] == 'set' and received_json["command"] == 'stop_tnc':
|
||||
try:
|
||||
static.TNCPROCESS.kill()
|
||||
# unregister process from atexit to avoid process zombies
|
||||
atexit.unregister(static.TNCPROCESS.kill)
|
||||
|
||||
structlog.get_logger("structlog").warning("[DMN] Stopping TNC")
|
||||
static.TNCSTARTED = False
|
||||
command_response("stop_tnc", True)
|
||||
except Exception as e:
|
||||
command_response("stop_tnc", False)
|
||||
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
|
||||
|
||||
def send_daemon_state():
|
||||
"""
|
||||
send the daemon state to network
|
||||
"""
|
||||
try:
|
||||
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1])
|
||||
|
||||
output = {
|
||||
'command': 'daemon_state',
|
||||
'daemon_state': [],
|
||||
'python_version': str(python_version),
|
||||
'hamlib_version': static.HAMLIB_VERSION,
|
||||
'input_devices': static.AUDIO_INPUT_DEVICES,
|
||||
'output_devices': static.AUDIO_OUTPUT_DEVICES,
|
||||
'serial_devices': static.SERIAL_DEVICES,
|
||||
#'cpu': str(psutil.cpu_percent()),
|
||||
#'ram': str(psutil.virtual_memory().percent),
|
||||
'version': '0.1'
|
||||
}
|
||||
|
||||
if static.TNCSTARTED:
|
||||
output["daemon_state"].append({"status": "running"})
|
||||
else:
|
||||
output["daemon_state"].append({"status": "stopped"})
|
||||
|
||||
|
||||
jsondata = json.dumps(output)
|
||||
|
||||
return jsondata
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def command_response(command, status):
|
||||
if status:
|
||||
status = "OK"
|
||||
else:
|
||||
status = "Failed"
|
||||
|
||||
jsondata = {"command_response": command, "status" : status}
|
||||
data_out = json.dumps(jsondata)
|
||||
SOCKET_QUEUE.put(data_out)
|
||||
|
|
|
@ -5,9 +5,11 @@ Created on Wed Dec 23 11:13:57 2020
|
|||
|
||||
@author: DJ2LS
|
||||
Here we are saving application wide variables and stats, which have to be accessed everywhere.
|
||||
Not nice, tipps are appreciated :-)
|
||||
Not nice, suggestions are appreciated :-)
|
||||
"""
|
||||
|
||||
VERSION = '0.1.0-alpha'
|
||||
|
||||
# DAEMON
|
||||
DAEMONPORT = 3001
|
||||
TNCSTARTED = False
|
||||
|
@ -16,14 +18,15 @@ TNCPROCESS = 0
|
|||
|
||||
# Operator Defaults
|
||||
MYCALLSIGN = b'AA0AA'
|
||||
MYCALLSIGN_CRC8 = b'A'
|
||||
MYCALLSIGN_CRC = b'A'
|
||||
|
||||
DXCALLSIGN = b'AA0AA'
|
||||
DXCALLSIGN_CRC8 = b'A'
|
||||
DXCALLSIGN_CRC = b'A'
|
||||
|
||||
MYGRID = b''
|
||||
DXGRID = b''
|
||||
|
||||
LOW_BANDWITH_MODE = False
|
||||
# ---------------------------------
|
||||
|
||||
# Server Defaults
|
||||
|
@ -31,12 +34,14 @@ HOST = "0.0.0.0"
|
|||
PORT = 3000
|
||||
SOCKET_TIMEOUT = 1 # seconds
|
||||
# ---------------------------------
|
||||
|
||||
SERIAL_DEVICES = []
|
||||
# ---------------------------------
|
||||
|
||||
|
||||
PTT_STATE = False
|
||||
TRANSMITTING = False
|
||||
|
||||
HAMLIB_VERSION = '0'
|
||||
HAMLIB_PTT_TYPE = 'RTS'
|
||||
HAMLIB_DEVICE_NAME = 'RIG_MODEL_DUMMY_NOVFO'
|
||||
HAMLIB_DEVICE_PORT = '/dev/ttyUSB0'
|
||||
|
@ -46,8 +51,8 @@ HAMLIB_STOP_BITS = '1'
|
|||
HAMLIB_DATA_BITS = '8'
|
||||
HAMLIB_HANDSHAKE = 'None'
|
||||
HAMLIB_RADIOCONTROL = 'direct'
|
||||
HAMLIB_RGICTLD_IP = '127.0.0.1'
|
||||
HAMLIB_RGICTLD_PORT = '4532'
|
||||
HAMLIB_RIGCTLD_IP = '127.0.0.1'
|
||||
HAMLIB_RIGCTLD_PORT = '4532'
|
||||
|
||||
HAMLIB_FREQUENCY = 0
|
||||
HAMLIB_MODE = ''
|
||||
|
@ -58,16 +63,23 @@ HAMLIB_BANDWITH = 0
|
|||
SNR = 0
|
||||
FREQ_OFFSET = 0
|
||||
SCATTER = []
|
||||
ENABLE_SCATTER = False
|
||||
# ---------------------------------
|
||||
|
||||
# Audio Defaults
|
||||
AUDIO_INPUT_DEVICES = []
|
||||
AUDIO_OUTPUT_DEVICES = []
|
||||
AUDIO_INPUT_DEVICE = -2
|
||||
AUDIO_OUTPUT_DEVICE = -2
|
||||
BUFFER_OVERFLOW_COUNTER = [0,0,0]
|
||||
|
||||
AUDIO_RMS = 0
|
||||
FFT = []
|
||||
FFT = [0]
|
||||
ENABLE_FFT = False
|
||||
CHANNEL_BUSY = None
|
||||
|
||||
# ARQ PROTOCOL VERSION
|
||||
ARQ_PROTOCOL_VERSION = 0
|
||||
|
||||
# ARQ statistics
|
||||
ARQ_BYTES_PER_MINUTE_BURST = 0
|
||||
|
@ -76,15 +88,19 @@ ARQ_BITS_PER_SECOND_BURST = 0
|
|||
ARQ_BITS_PER_SECOND = 0
|
||||
ARQ_COMPRESSION_FACTOR = 0
|
||||
ARQ_TRANSMISSION_PERCENT = 0
|
||||
ARQ_SPEED_LEVEL = 0
|
||||
TOTAL_BYTES = 0
|
||||
|
||||
|
||||
#CHANNEL_STATE = 'RECEIVING_SIGNALLING'
|
||||
TNC_STATE = 'IDLE'
|
||||
ARQ_STATE = False
|
||||
ARQ_SESSION = False
|
||||
ARQ_SESSION_STATE = 'disconnected' # disconnected, connecting, connected, disconnecting, failed
|
||||
|
||||
# BEACON STATE
|
||||
BEACON_STATE = False
|
||||
BEACON_PAUSE = False
|
||||
|
||||
# ------- RX BUFFER
|
||||
RX_BUFFER = []
|
||||
|
|
Loading…
Reference in a new issue