mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Compare commits
275 commits
ec7519400a
...
f7bcdad47f
Author | SHA1 | Date | |
---|---|---|---|
|
f7bcdad47f | ||
|
1b90d38ebf | ||
|
5a3f367a33 | ||
|
6ae542230f | ||
|
f18604a38b | ||
|
caaf613446 | ||
|
9b13efacba | ||
|
4ea78356b6 | ||
|
545aaf031f | ||
|
3086a42da9 | ||
|
6b853511f4 | ||
|
9a772e28c2 | ||
|
eaca7c168e | ||
|
4e83d49144 | ||
|
6e863ddd22 | ||
|
19a473c577 | ||
|
7f7061dae3 | ||
|
432d091c97 | ||
|
8e68aef7f9 | ||
|
0635d35443 | ||
|
1686f554b1 | ||
|
679da34ed7 | ||
|
caa0bb2aac | ||
|
44bf16f15e | ||
|
7794f1afd2 | ||
|
7a32e464f4 | ||
|
6dfc85e983 | ||
|
3d8fe47270 | ||
|
eb3c559ce2 | ||
|
d4a821ac39 | ||
|
47dd3fc23a | ||
|
0e465d8bbb | ||
|
3044759569 | ||
|
2a8d1fd471 | ||
|
1d14f51854 | ||
|
da6dd1f045 | ||
|
21c98a8366 | ||
|
8e9b9c9199 | ||
|
059d23fa31 | ||
|
784aa9cf82 | ||
|
440fda917a | ||
|
7326f871e5 | ||
|
4687dd36e7 | ||
|
93d238d4e1 | ||
|
4c9ee09e86 | ||
|
1bd6537d64 | ||
|
97143f25f6 | ||
|
72d412891e | ||
|
94b4889478 | ||
|
1de42f6ff1 | ||
|
310479fef4 | ||
|
3c0484cf46 | ||
|
0ee9671a7d | ||
|
1c7d9d8d0c | ||
|
81d434bfd1 | ||
|
6dcfe17c5f | ||
|
4904c5e7df | ||
|
32b181b3b0 | ||
|
da1309a335 | ||
|
6ab2f0ce54 | ||
|
4e46577a57 | ||
|
ab96c8bc51 | ||
|
4988a92f52 | ||
|
44395c32f4 | ||
|
034577144b | ||
|
9497d323c8 | ||
|
41a45a9cd9 | ||
|
4a76e5fc1e | ||
|
3727c59584 | ||
|
aba556a996 | ||
|
cc61f6a602 | ||
|
8c63259c26 | ||
|
11826c492d | ||
|
796294f03e | ||
|
5c50ca4803 | ||
|
d54f3dcb31 | ||
|
53697af6e2 | ||
|
ee4528bf79 | ||
|
ac1a82fc5e | ||
|
93d61b0f25 | ||
|
252fe73d68 | ||
|
b1f4dadcc6 | ||
|
df52828207 | ||
|
7ba1dbadb2 | ||
|
5d8b8a4bde | ||
|
03705a041c | ||
|
501ccd9de3 | ||
|
dc4ea2c5dd | ||
|
250943f63f | ||
|
4264f7d9bb | ||
|
bdc407aff9 | ||
|
7831cdaa16 | ||
|
4c53cdc79b | ||
|
bbb08ed7af | ||
|
756101ebe1 | ||
|
a43d90f7d8 | ||
|
28e8aaa595 | ||
|
36f80a5b0a | ||
|
5c19d6f90e | ||
|
d2dc5c98d4 | ||
|
1bd1781dd5 | ||
|
aa6f787630 | ||
|
2cb7ee7893 | ||
|
4ddfe52db9 | ||
|
ab44e18e3e | ||
|
e05577b492 | ||
|
5be2a88fd0 | ||
|
a3f9dd6f45 | ||
|
9c57cc9eb7 | ||
|
0d13b638ad | ||
|
71051fd3fa | ||
|
4d2d0b93ac | ||
|
3b8236826c | ||
|
00d007b9b7 | ||
|
2392ff3b53 | ||
|
17977b5281 | ||
|
8717629b91 | ||
|
47e177f399 | ||
|
e30f09f4de | ||
|
586c02f73b | ||
|
751e43eddb | ||
|
37267ac679 | ||
|
f2e5a11348 | ||
|
efd92cdd72 | ||
|
2ba8b77ea7 | ||
|
4cce8aec5c | ||
|
58342b975f | ||
|
c6d99f8866 | ||
|
c8eb9bbf92 | ||
|
c5a9229207 | ||
|
b637b917bc | ||
|
3dc06510c1 | ||
|
76522db082 | ||
|
d6df3007db | ||
|
5a0a766aa0 | ||
|
eaa16ed50b | ||
|
281731d890 | ||
|
d572772df3 | ||
|
9dad094e47 | ||
|
cb78ed984a | ||
|
fd402d9bc2 | ||
|
9a4401082c | ||
|
c78fff4db1 | ||
|
d78fcba4fb | ||
|
7ed43fb3f9 | ||
|
f5a30e33e3 | ||
|
a9139d035c | ||
|
62309d608d | ||
|
4d8b8f7f46 | ||
|
7691ba09ac | ||
|
c0e4f14da0 | ||
|
424384c7ed | ||
|
e2d4b58e30 | ||
|
76f24f2b31 | ||
|
af851d15f3 | ||
|
9ef19b7c51 | ||
|
914e2065b5 | ||
|
f896dd84c5 | ||
|
f12bf7919d | ||
|
dcec4a4d17 | ||
|
a946ca6555 | ||
|
0f984c26b2 | ||
|
10d337b962 | ||
|
855159d150 | ||
|
443d7931b7 | ||
|
33e0c2d497 | ||
|
af7c9cbafb | ||
|
a546f0f387 | ||
|
43eff325d1 | ||
|
eba1cde5b4 | ||
|
d26186e6fd | ||
|
495b988701 | ||
|
f730fbe3d7 | ||
|
6b60fd4275 | ||
|
d8255fbd2f | ||
|
c143c87d64 | ||
|
f979fb3d7b | ||
|
934bb20010 | ||
|
9d26472607 | ||
|
847c3928df | ||
|
f64c4ff0dd | ||
|
1937606526 | ||
|
771afabd7d | ||
|
e069be45ba | ||
|
242b4adc5f | ||
|
12d1477c36 | ||
|
e672218594 | ||
|
9c8237f505 | ||
|
db9c5b60b9 | ||
|
179f4f7265 | ||
|
dfa07f9e77 | ||
|
50bbcfd7ff | ||
|
5d8b4f2d67 | ||
|
819832127e | ||
|
6e3edb5b30 | ||
|
10b925d70b | ||
|
2cdea7eaa9 | ||
|
19bcde01b0 | ||
|
2e12b2ed92 | ||
|
bdeba55f8e | ||
|
11512d76bf | ||
|
d9a7d392ce | ||
|
8c0c13f227 | ||
|
ddf6cb0ee0 | ||
|
3d6bf72633 | ||
|
331102e03d | ||
|
cc75d351ab | ||
|
f2dd278bce | ||
|
af889f9460 | ||
|
dba907dabf | ||
|
75c514f342 | ||
|
362cef9b08 | ||
|
56701347fc | ||
|
51d7c681a6 | ||
|
1b1bb18bb2 | ||
|
4c611731a8 | ||
|
db315bae72 | ||
|
c499a6a524 | ||
|
9dcf0959b4 | ||
|
d0d853d72b | ||
|
85b16c7935 | ||
|
b543c914a0 | ||
|
4c69b748d5 | ||
|
83b3f6a71c | ||
|
12933e96b7 | ||
|
90c4bf9360 | ||
|
29c1d0ad72 | ||
|
c3f198fda9 | ||
|
eeffb8c6e4 | ||
|
9468ceb6fc | ||
|
02c08a71a8 | ||
|
ac62fe4448 | ||
|
d5a0464fd9 | ||
|
a2fd010198 | ||
|
ea1b6cb27c | ||
|
2de80520e1 | ||
|
fa7360f1f3 | ||
|
9941c7ea4d | ||
|
6ea653c5aa | ||
|
8251b35ebb | ||
|
8b2412de96 | ||
|
4b5622647e | ||
|
7cd01e1e74 | ||
|
45a5e60b2b | ||
|
bc72e5f3ba | ||
|
ca955b1ff2 | ||
|
40beee630c | ||
|
37da268547 | ||
|
cf4b3bf9cd | ||
|
564f4c106e | ||
|
ba2e2b8ce9 | ||
|
cd2b0dc133 | ||
|
93f951663e | ||
|
1aa3073aa2 | ||
|
ce19b9c2d9 | ||
|
03d4cce4a7 | ||
|
966198a9c3 | ||
|
913bce2ea5 | ||
|
048b5c85b5 | ||
|
a9c415c59e | ||
|
a68d2c176a | ||
|
53d0383696 | ||
|
9053e2f57f | ||
|
f21069e29d | ||
|
18f670bac7 | ||
|
03f5f8dcce | ||
|
c93cba9bc0 | ||
|
a67d6cb450 | ||
|
5516985f49 | ||
|
7efad9479c | ||
|
b125f36a13 | ||
|
32a01a5859 | ||
|
63d47cb745 | ||
|
b97db66736 | ||
|
19bff8cb4e |
36 changed files with 2481 additions and 704 deletions
118
.github/workflows/build_multiplatform.yml
vendored
118
.github/workflows/build_multiplatform.yml
vendored
|
@ -100,9 +100,6 @@ jobs:
|
||||||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||||
path: codec2/tempfiles/*
|
path: codec2/tempfiles/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BUILD_ARM:
|
BUILD_ARM:
|
||||||
# The host should always be linux
|
# The host should always be linux
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -199,6 +196,7 @@ jobs:
|
||||||
name: Build FreeDATA packages
|
name: Build FreeDATA packages
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, macos-11, windows-latest]
|
os: [ubuntu-20.04, macos-11, windows-latest]
|
||||||
include:
|
include:
|
||||||
|
@ -236,7 +234,7 @@ jobs:
|
||||||
- name: Install Node.js, NPM and Yarn
|
- name: Install Node.js, NPM and Yarn
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
|
|
||||||
- name: Create tnc/dist
|
- name: Create tnc/dist
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
|
@ -268,7 +266,7 @@ jobs:
|
||||||
# if: matrix.os == 'ubuntu-20.04'
|
# if: matrix.os == 'ubuntu-20.04'
|
||||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||||
run: |
|
run: |
|
||||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2
|
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||||
|
|
||||||
- name: Install MacOS pyAudio
|
- name: Install MacOS pyAudio
|
||||||
if: ${{startsWith(matrix.os, 'macos')}}
|
if: ${{startsWith(matrix.os, 'macos')}}
|
||||||
|
@ -282,27 +280,25 @@ jobs:
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Add MacOS certs
|
||||||
|
if: ${{startsWith(matrix.os, 'macos')}}
|
||||||
|
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
|
||||||
|
env:
|
||||||
|
CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
|
||||||
|
|
||||||
# - name: Install Pyaudio Windows
|
- name: Build binaries macOS
|
||||||
# if: ${{startsWith(matrix.os, 'windows')}}
|
if: ${{startsWith(matrix.os, 'macos')}}
|
||||||
# working-directory: tnc/lib/pyaudio/windows
|
working-directory: tnc
|
||||||
# run: |
|
run: |
|
||||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
# now build tnc binaries
|
||||||
|
pyinstaller -y freedata.spec
|
||||||
|
# and to some final cleanup
|
||||||
|
# cp -r -f dist/tnc/* dist/
|
||||||
|
# rm -r dist/tnc
|
||||||
|
|
||||||
# - name: Display structure of downloaded files
|
- name: Build binaries Linux and Windows
|
||||||
# run: ls -R
|
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||||
|
|
||||||
# - name: cleanup codec2
|
|
||||||
# working-directory: tnc/lib/
|
|
||||||
# run: |
|
|
||||||
# mkdir codec2
|
|
||||||
# cd codec2
|
|
||||||
|
|
||||||
# - uses: actions/download-artifact@v3
|
|
||||||
# with:
|
|
||||||
# path: tnc/lib/codec2
|
|
||||||
|
|
||||||
- name: Build binaries
|
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
# pyinstaller freedata.spec
|
# pyinstaller freedata.spec
|
||||||
|
@ -311,8 +307,8 @@ jobs:
|
||||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
|
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
|
||||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
|
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
|
||||||
|
|
||||||
- name: Copy binaries - Linux and MacOS
|
- name: Copy binaries - Linux
|
||||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
cp -r -f daemon.dist/* dist/tnc
|
cp -r -f daemon.dist/* dist/tnc
|
||||||
|
@ -327,6 +323,8 @@ jobs:
|
||||||
cp -r -Force main.dist/* dist/tnc
|
cp -r -Force main.dist/* dist/tnc
|
||||||
|
|
||||||
- name: Rename tnc binaries
|
- name: Rename tnc binaries
|
||||||
|
# we don't need renaming for pyinstaller builds as output name is defined
|
||||||
|
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
||||||
|
@ -336,62 +334,43 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: tnc/dist/tnc
|
path: tnc/dist/tnc
|
||||||
|
|
||||||
|
|
||||||
- name: LIST ALL FILES
|
- name: LIST ALL FILES
|
||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Download Portaudio binaries
|
- name: Download Portaudio binaries Linux macOS
|
||||||
|
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||||
working-directory: tnc
|
working-directory: tnc
|
||||||
run: |
|
run: |
|
||||||
|
if ! test -d "dist/tnc/_sounddevice_data"; then
|
||||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Download Portaudio binaries Windows
|
||||||
|
if: ${{startsWith(matrix.os, 'windows')}}
|
||||||
|
working-directory: tnc
|
||||||
|
run: |
|
||||||
|
if(Test-Path -Path "dist/_sounddevice_data"){
|
||||||
|
echo "sounddevice folder already exists"
|
||||||
|
} else {
|
||||||
|
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||||
|
}
|
||||||
|
|
||||||
- name: LIST ALL FILES
|
- name: LIST ALL FILES
|
||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
#- name: Compress TNC
|
- name: cleanup on macos before code signing
|
||||||
# # if: ${{!startsWith(matrix.os, 'windows') }}
|
if: ${{startsWith(matrix.os, 'macos')}}
|
||||||
# shell: bash
|
run: |
|
||||||
# run: |
|
ls -l
|
||||||
# cd ./tnc/dist
|
# find . -type d -name .git -exec rm -r {} \;
|
||||||
# zip -r ./${{ matrix.zip_name }}.zip *
|
find . -type d -o -name ".git" -delete
|
||||||
|
|
||||||
##- name: Copy TNC to GUI
|
|
||||||
## run: |
|
|
||||||
## # cp -R ./tnc/dist/tnc ./gui/tnc
|
|
||||||
## cp -R ./tnc/dist ./gui/tnc
|
|
||||||
|
|
||||||
##- name: LIST ALL FILES
|
|
||||||
## run: ls -R
|
|
||||||
|
|
||||||
##- name: Compress TNC
|
|
||||||
## uses: thedoctor0/zip-release@master
|
|
||||||
## with:
|
|
||||||
## type: 'zip'
|
|
||||||
## filename: '${{ matrix.zip_name }}'
|
|
||||||
## # directory: ./tnc/dist/tnc
|
|
||||||
## directory: ./tnc/dist/tnc
|
|
||||||
## path: .
|
|
||||||
## # exclusions: '*.git* /*node_modules/* .editorconfig'
|
|
||||||
|
|
||||||
##- name: LIST ALL FILES
|
|
||||||
## run: ls -R
|
|
||||||
|
|
||||||
##- name: Upload TNC artifacts
|
|
||||||
## uses: actions/upload-artifact@v3
|
|
||||||
## with:
|
|
||||||
## name: ${{ matrix.zip_name }}.zip
|
|
||||||
## # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
|
||||||
## path: ./tnc/dist/${{ matrix.zip_name }}.zip
|
|
||||||
|
|
||||||
|
|
||||||
##- 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
|
|
||||||
## #files: ./tnc/dist/${{ matrix.zip_name }}.zip
|
|
||||||
|
|
||||||
- name: Build/release Electron app
|
- name: Build/release Electron app
|
||||||
uses: samuelmeuli/action-electron-builder@v1
|
uses: samuelmeuli/action-electron-builder@v1
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
package_root: "./gui/"
|
package_root: "./gui/"
|
||||||
github_token: ${{ secrets.github_token }}
|
github_token: ${{ secrets.github_token }}
|
||||||
|
@ -399,6 +378,7 @@ jobs:
|
||||||
# release the app after building
|
# release the app after building
|
||||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
args: ${{ matrix.electron_parameters }}
|
args: ${{ matrix.electron_parameters }}
|
||||||
|
max_attempts: 3
|
||||||
|
|
||||||
- name: Compress TNC
|
- name: Compress TNC
|
||||||
uses: thedoctor0/zip-release@master
|
uses: thedoctor0/zip-release@master
|
||||||
|
|
1
.github/workflows/ctest.yml
vendored
1
.github/workflows/ctest.yml
vendored
|
@ -21,6 +21,7 @@ jobs:
|
||||||
- python-version: "3.9"
|
- python-version: "3.9"
|
||||||
- python-version: "3.10"
|
- python-version: "3.10"
|
||||||
- python-version: "3.11"
|
- python-version: "3.11"
|
||||||
|
- python-version: "3.12-dev"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
23
add-osx-cert.sh
Normal file
23
add-osx-cert.sh
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
KEY_CHAIN=build.keychain
|
||||||
|
CERTIFICATE_P12=certificate.p12
|
||||||
|
|
||||||
|
# Recreate the certificate from the secure environment variable
|
||||||
|
echo $CERTIFICATE_OSX_APPLICATION | base64 --decode > $CERTIFICATE_P12
|
||||||
|
|
||||||
|
#create a keychain
|
||||||
|
security create-keychain -p actions $KEY_CHAIN
|
||||||
|
|
||||||
|
# Make the keychain the default so identities are found
|
||||||
|
security default-keychain -s $KEY_CHAIN
|
||||||
|
|
||||||
|
# Unlock the keychain
|
||||||
|
security unlock-keychain -p actions $KEY_CHAIN
|
||||||
|
|
||||||
|
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign;
|
||||||
|
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
|
||||||
|
|
||||||
|
# remove certs
|
||||||
|
rm -fr *.p12
|
16
gui/build/entitlements.plist
Normal file
16
gui/build/entitlements.plist
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.automation.apple-events</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -92,7 +92,8 @@ const configDefaultSettings = '{\
|
||||||
"tuning_range_fmax" : "50.0",\
|
"tuning_range_fmax" : "50.0",\
|
||||||
"respond_to_cq" : "True",\
|
"respond_to_cq" : "True",\
|
||||||
"rx_buffer_size" : "16", \
|
"rx_buffer_size" : "16", \
|
||||||
"enable_explorer" : "False" \
|
"enable_explorer" : "False", \
|
||||||
|
"wftheme": 2 \
|
||||||
}';
|
}';
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "FreeDATA",
|
"name": "FreeDATA",
|
||||||
"version": "0.6.7-alpha.1",
|
"version": "0.6.12-alpha.6",
|
||||||
"description": "FreeDATA ",
|
"description": "FreeDATA ",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -28,50 +28,54 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://freedata.app",
|
"homepage": "https://freedata.app",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@electron/asar": "^3.2.3",
|
||||||
|
"@electron/osx-sign": "^1.0.4",
|
||||||
|
"@popperjs/core": "^2.11.6",
|
||||||
"blob-util": "^2.0.2",
|
"blob-util": "^2.0.2",
|
||||||
"bootstrap": "^5.2.1",
|
"bootstrap": "^5.2.1",
|
||||||
"bootstrap-icons": "^1.9.1",
|
"bootstrap-icons": "^1.9.1",
|
||||||
"bootswatch": "^5.2.0",
|
"bootswatch": "^5.2.0",
|
||||||
"chart.js": "^3.9.1",
|
"chart.js": "^4.0.0",
|
||||||
"chartjs-plugin-annotation": "^2.0.1",
|
"chartjs-plugin-annotation": "^2.1.2",
|
||||||
"electron-log": "^4.4.8",
|
"electron-log": "^4.4.8",
|
||||||
"electron-updater": "^5.2.1",
|
"electron-updater": "^5.2.1",
|
||||||
"emoji-picker-element": "^1.12.1",
|
"emoji-picker-element": "^1.12.1",
|
||||||
"emoji-picker-element-data": "^1.3.0",
|
"emoji-picker-element-data": "^1.3.0",
|
||||||
|
"express-pouchdb": "^4.2.0",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"pouchdb": "^7.3.0",
|
"pouchdb": "^7.3.0",
|
||||||
"pouchdb-browser": "^7.3.0",
|
"pouchdb-browser": "^7.3.0",
|
||||||
|
"pouchdb-express-router": "^0.0.11",
|
||||||
"pouchdb-find": "^7.3.0",
|
"pouchdb-find": "^7.3.0",
|
||||||
|
"pouchdb-replication": "^8.0.0",
|
||||||
"qth-locator": "^2.1.0",
|
"qth-locator": "^2.1.0",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@electron/notarize": "^1.2.3",
|
||||||
"electron": "^20.1.3",
|
"electron": "^20.1.3",
|
||||||
"electron-builder": "^23.3.3"
|
"electron-builder": "^23.3.3",
|
||||||
|
"electron-builder-notarize": "^1.5.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"productName": "FreeDATA",
|
"productName": "FreeDATA",
|
||||||
"appId": "app.freedata",
|
"appId": "app.freedata",
|
||||||
|
"afterSign": "electron-builder-notarize",
|
||||||
"npmRebuild": "false",
|
"npmRebuild": "false",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "build",
|
"buildResources": "build",
|
||||||
"output": "dist"
|
"output": "dist"
|
||||||
},
|
},
|
||||||
"dmg": {
|
"mac": {
|
||||||
|
"target": [
|
||||||
|
"default"
|
||||||
|
],
|
||||||
"icon": "build/icon.png",
|
"icon": "build/icon.png",
|
||||||
"contents": [
|
"hardenedRuntime": true,
|
||||||
{
|
"entitlements": "build/entitlements.plist",
|
||||||
"x": 130,
|
"entitlementsInherit": "build/entitlements.plist",
|
||||||
"y": 220
|
"gatekeeperAssess": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": 410,
|
|
||||||
"y": 220,
|
|
||||||
"type": "link",
|
|
||||||
"path": "/Applications"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "build/icon.png",
|
"icon": "build/icon.png",
|
||||||
|
@ -95,7 +99,8 @@
|
||||||
"from": "../tnc/dist/tnc/",
|
"from": "../tnc/dist/tnc/",
|
||||||
"to": "tnc",
|
"to": "tnc",
|
||||||
"filter": [
|
"filter": [
|
||||||
"**/*"
|
"**/*",
|
||||||
|
"!**/.git"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -34,7 +34,7 @@ const dateFormatHours = new Intl.DateTimeFormat('en-GB', {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
});
|
});
|
||||||
// split character
|
// split character
|
||||||
const split_char = '\0;'
|
const split_char = '\0;\1;'
|
||||||
// global for our selected file we want to transmit
|
// global for our selected file we want to transmit
|
||||||
// ----------------- some chat globals
|
// ----------------- some chat globals
|
||||||
var filetype = '';
|
var filetype = '';
|
||||||
|
@ -61,25 +61,51 @@ try{
|
||||||
}
|
}
|
||||||
|
|
||||||
PouchDB.plugin(require('pouchdb-find'));
|
PouchDB.plugin(require('pouchdb-find'));
|
||||||
var db = new PouchDB(chatDB);
|
//PouchDB.plugin(require('pouchdb-replication'));
|
||||||
var remoteDB = new PouchDB('http://192.168.178.79:5984/chatDB')
|
|
||||||
|
|
||||||
db.sync(remoteDB, {
|
var db = new PouchDB(chatDB);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// REMOTE SYNC ATTEMPTS
|
||||||
|
|
||||||
|
|
||||||
|
var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB')
|
||||||
|
|
||||||
|
// we need express packages for running pouchdb sync "express-pouchdb"
|
||||||
|
var express = require('express');
|
||||||
|
var app = express();
|
||||||
|
//app.use('/chatDB', require('express-pouchdb')(PouchDB));
|
||||||
|
//app.listen(5984);
|
||||||
|
|
||||||
|
app.use('/chatDB', require('pouchdb-express-router')(PouchDB));
|
||||||
|
app.listen(5984);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
db.sync('http://172.20.10.4:5984/jojo', {
|
||||||
|
//var sync = PouchDB.sync('chatDB', 'http://172.20.10.4:5984/chatDB', {
|
||||||
live: true,
|
live: true,
|
||||||
retry: true
|
retry: false
|
||||||
}).on('change', function (change) {
|
}).on('change', function (change) {
|
||||||
// yo, something changed!
|
// yo, something changed!
|
||||||
console.log(change)
|
console.log(change)
|
||||||
}).on('paused', function (info) {
|
}).on('paused', function (err) {
|
||||||
// replication was paused, usually because of a lost connection
|
// replication was paused, usually because of a lost connection
|
||||||
console.log(info)
|
console.log(err)
|
||||||
}).on('active', function (info) {
|
}).on('active', function (info) {
|
||||||
// replication was resumed
|
// replication was resumed
|
||||||
console.log(info)
|
console.log(info)
|
||||||
}).on('error', function (err) {
|
}).on('error', function (err) {
|
||||||
// totally unhandled error (shouldn't happen)
|
// totally unhandled error (shouldn't happen)
|
||||||
console.log(error)
|
console.log(err)
|
||||||
|
}).on('denied', function (err) {
|
||||||
|
// a document failed to replicate (e.g. due to permissions)
|
||||||
|
console.log(err)
|
||||||
|
}).on('complete', function (info) {
|
||||||
|
// handle complete;
|
||||||
|
console.log(info)
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
var dxcallsigns = new Set();
|
var dxcallsigns = new Set();
|
||||||
db.createIndex({
|
db.createIndex({
|
||||||
|
@ -297,7 +323,9 @@ db.post({
|
||||||
}
|
}
|
||||||
var timestamp = Math.floor(Date.now() / 1000);
|
var timestamp = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
var data_with_attachment = chatmessage + split_char + filename + split_char + filetype + split_char + file + split_char + timestamp;
|
var file_checksum = crc32(file).toString(16).toUpperCase();
|
||||||
|
console.log(file_checksum)
|
||||||
|
var data_with_attachment = timestamp + split_char + chatmessage + split_char + filename + split_char + filetype + split_char + file;
|
||||||
|
|
||||||
document.getElementById('selectFilesButton').innerHTML = ``;
|
document.getElementById('selectFilesButton').innerHTML = ``;
|
||||||
var uuid = uuidv4();
|
var uuid = uuidv4();
|
||||||
|
@ -308,7 +336,7 @@ db.post({
|
||||||
mode: 255,
|
mode: 255,
|
||||||
frames: 1,
|
frames: 1,
|
||||||
data: data_with_attachment,
|
data: data_with_attachment,
|
||||||
checksum: '123',
|
checksum: file_checksum,
|
||||||
uuid: uuid
|
uuid: uuid
|
||||||
};
|
};
|
||||||
ipcRenderer.send('run-tnc-command', Data);
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
@ -318,7 +346,7 @@ db.post({
|
||||||
dxcallsign: dxcallsign,
|
dxcallsign: dxcallsign,
|
||||||
dxgrid: 'null',
|
dxgrid: 'null',
|
||||||
msg: chatmessage,
|
msg: chatmessage,
|
||||||
checksum: 'null',
|
checksum: file_checksum,
|
||||||
type: "transmit",
|
type: "transmit",
|
||||||
status: 'transmit',
|
status: 'transmit',
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
|
@ -404,7 +432,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
|
|
||||||
//handle ping
|
//handle ping
|
||||||
if (item.ping == 'received') {
|
if (item.ping == 'received') {
|
||||||
obj.timestamp = item.timestamp;
|
obj.timestamp = parseInt(item.timestamp);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.uuid = item.uuid;
|
obj.uuid = item.uuid;
|
||||||
|
@ -421,12 +449,9 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
add_obj_to_database(obj)
|
add_obj_to_database(obj)
|
||||||
update_chat_obj_by_uuid(obj.uuid);
|
update_chat_obj_by_uuid(obj.uuid);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// handle beacon
|
// handle beacon
|
||||||
} else if (item.beacon == 'received') {
|
} else if (item.beacon == 'received') {
|
||||||
obj.timestamp = item.timestamp;
|
obj.timestamp = parseInt(item.timestamp);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.uuid = item.uuid;
|
obj.uuid = item.uuid;
|
||||||
|
@ -451,20 +476,20 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
|
|
||||||
console.log(splitted_data)
|
console.log(splitted_data)
|
||||||
|
|
||||||
obj.timestamp = splitted_data[8];
|
obj.timestamp = parseInt(splitted_data[4]);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.command = splitted_data[1];
|
obj.command = splitted_data[1];
|
||||||
obj.checksum = splitted_data[2];
|
obj.checksum = splitted_data[2];
|
||||||
// convert message to unicode from utf8 because of emojis
|
// convert message to unicode from utf8 because of emojis
|
||||||
obj.uuid = utf8.decode(splitted_data[3]);
|
obj.uuid = utf8.decode(splitted_data[3]);
|
||||||
obj.msg = utf8.decode(splitted_data[4]);
|
obj.msg = utf8.decode(splitted_data[5]);
|
||||||
obj.status = 'null';
|
obj.status = 'null';
|
||||||
obj.snr = 'null';
|
obj.snr = 'null';
|
||||||
obj.type = 'received';
|
obj.type = 'received';
|
||||||
obj.filename = utf8.decode(splitted_data[5]);
|
obj.filename = utf8.decode(splitted_data[6]);
|
||||||
obj.filetype = utf8.decode(splitted_data[6]);
|
obj.filetype = utf8.decode(splitted_data[7]);
|
||||||
obj.file = btoa(utf8.decode(splitted_data[7]));
|
obj.file = btoa(splitted_data[8]);
|
||||||
|
|
||||||
add_obj_to_database(obj);
|
add_obj_to_database(obj);
|
||||||
update_chat_obj_by_uuid(obj.uuid);
|
update_chat_obj_by_uuid(obj.uuid);
|
||||||
|
@ -827,16 +852,21 @@ update_chat = function(obj) {
|
||||||
|
|
||||||
//var file = atob(obj._attachments[filename]["data"])
|
//var file = atob(obj._attachments[filename]["data"])
|
||||||
db.getAttachment(obj._id, filename).then(function(data) {
|
db.getAttachment(obj._id, filename).then(function(data) {
|
||||||
|
console.log(data)
|
||||||
|
// convert blob data to binary string
|
||||||
|
blobUtil.blobToBinaryString(data).then(function (binaryString) {
|
||||||
|
console.log(binaryString)
|
||||||
|
}).catch(function (err) {
|
||||||
|
// error
|
||||||
|
console.log(err);
|
||||||
|
binaryString = blobUtil.arrayBufferToBinaryString(data);
|
||||||
|
|
||||||
var file = blobUtil.arrayBufferToBinaryString(data)
|
}).then(function(){
|
||||||
|
|
||||||
// converting back to blob for debugging
|
console.log(binaryString)
|
||||||
// length must be equal of file size
|
console.log(binaryString.length)
|
||||||
var blob = blobUtil.binaryStringToBlob(file);
|
|
||||||
console.log(blob)
|
|
||||||
|
|
||||||
|
var data_with_attachment = doc.timestamp + split_char + utf8.encode(doc.msg) + split_char + filename + split_char + filetype + split_char + binaryString;
|
||||||
var data_with_attachment = doc.msg + split_char + filename + split_char + filetype + split_char + file + split_char + doc.timestamp;
|
|
||||||
let Data = {
|
let Data = {
|
||||||
command: "send_message",
|
command: "send_message",
|
||||||
dxcallsign: doc.dxcallsign,
|
dxcallsign: doc.dxcallsign,
|
||||||
|
@ -850,6 +880,7 @@ update_chat = function(obj) {
|
||||||
ipcRenderer.send('run-tnc-command', Data);
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
@ -935,7 +966,7 @@ update_chat_obj_by_uuid = function(uuid) {
|
||||||
add_obj_to_database = function(obj){
|
add_obj_to_database = function(obj){
|
||||||
db.put({
|
db.put({
|
||||||
_id: obj.uuid,
|
_id: obj.uuid,
|
||||||
timestamp: obj.timestamp,
|
timestamp: parseInt(obj.timestamp),
|
||||||
uuid: obj.uuid,
|
uuid: obj.uuid,
|
||||||
dxcallsign: obj.dxcallsign,
|
dxcallsign: obj.dxcallsign,
|
||||||
dxgrid: obj.dxgrid,
|
dxgrid: obj.dxgrid,
|
||||||
|
@ -965,3 +996,36 @@ function scrollMessagesToBottom() {
|
||||||
var messageBody = document.getElementById('message-container');
|
var messageBody = document.getElementById('message-container');
|
||||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// CRC CHECKSUMS
|
||||||
|
// https://stackoverflow.com/a/50579690
|
||||||
|
// crc32 calculation
|
||||||
|
//console.log(crc32('abc'));
|
||||||
|
//var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};
|
||||||
|
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||||
|
|
||||||
|
var makeCRCTable = function(){
|
||||||
|
var c;
|
||||||
|
var crcTable = [];
|
||||||
|
for(var n =0; n < 256; n++){
|
||||||
|
c = n;
|
||||||
|
for(var k =0; k < 8; k++){
|
||||||
|
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||||
|
}
|
||||||
|
crcTable[n] = c;
|
||||||
|
}
|
||||||
|
return crcTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
var crc32 = function(str) {
|
||||||
|
var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
|
||||||
|
var crc = 0 ^ (-1);
|
||||||
|
|
||||||
|
for (var i = 0; i < str.length; i++ ) {
|
||||||
|
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (crc ^ (-1)) >>> 0;
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {ipcRenderer} = require('electron');
|
const {ipcRenderer, shell} = require('electron');
|
||||||
const exec = require('child_process').spawn;
|
const exec = require('child_process').spawn;
|
||||||
const sock = require('./sock.js');
|
const sock = require('./sock.js');
|
||||||
const daemon = require('./daemon.js');
|
const daemon = require('./daemon.js');
|
||||||
|
@ -37,6 +37,87 @@ var dbfs_level_raw = 0
|
||||||
// WINDOW LISTENER
|
// WINDOW LISTENER
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// save frequency event listener
|
||||||
|
document.getElementById("saveFrequency").addEventListener("click", () => {
|
||||||
|
var freq = document.getElementById("newFrequency").value;
|
||||||
|
console.log(freq)
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "frequency",
|
||||||
|
frequency: freq,
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// enter button for input field
|
||||||
|
document.getElementById("newFrequency").addEventListener("keypress", function(event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
document.getElementById("saveFrequency").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// save mode event listener
|
||||||
|
document.getElementById("saveModePKTUSB").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "mode",
|
||||||
|
mode: "PKTUSB",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save mode event listener
|
||||||
|
document.getElementById("saveModeUSB").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "mode",
|
||||||
|
mode: "USB",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save mode event listener
|
||||||
|
document.getElementById("saveModeLSB").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "mode",
|
||||||
|
mode: "LSB",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save mode event listener
|
||||||
|
document.getElementById("saveModeAM").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "mode",
|
||||||
|
mode: "AM",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save mode event listener
|
||||||
|
document.getElementById("saveModeFM").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "mode",
|
||||||
|
mode: "FM",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// start stop audio recording event listener
|
||||||
|
document.getElementById("startStopRecording").addEventListener("click", () => {
|
||||||
|
let Data = {
|
||||||
|
type: "set",
|
||||||
|
command: "record_audio",
|
||||||
|
};
|
||||||
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('received_files_folder').addEventListener('click', () => {
|
document.getElementById('received_files_folder').addEventListener('click', () => {
|
||||||
|
|
||||||
|
@ -96,8 +177,8 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', ()
|
||||||
// hamlib settings
|
// hamlib settings
|
||||||
document.getElementById('hamlib_deviceid').value = config.hamlib_deviceid;
|
document.getElementById('hamlib_deviceid').value = config.hamlib_deviceid;
|
||||||
|
|
||||||
set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport)
|
set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport)
|
||||||
set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
|
set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
|
||||||
|
|
||||||
document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed;
|
document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed;
|
||||||
set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed)
|
set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed)
|
||||||
|
@ -208,16 +289,57 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
||||||
if (config.spectrum == 'waterfall') {
|
if (config.spectrum == 'waterfall') {
|
||||||
document.getElementById("waterfall-scatter-switch1").checked = true;
|
document.getElementById("waterfall-scatter-switch1").checked = true;
|
||||||
document.getElementById("waterfall-scatter-switch2").checked = false;
|
document.getElementById("waterfall-scatter-switch2").checked = false;
|
||||||
document.getElementById("scatter").style.visibility = 'hidden';
|
document.getElementById("waterfall-scatter-switch3").checked = false;
|
||||||
|
|
||||||
document.getElementById("waterfall").style.visibility = 'visible';
|
document.getElementById("waterfall").style.visibility = 'visible';
|
||||||
document.getElementById("waterfall").style.height = '100%';
|
document.getElementById("waterfall").style.height = '100%';
|
||||||
} else {
|
document.getElementById("waterfall").style.display = 'block';
|
||||||
|
|
||||||
|
document.getElementById("scatter").style.height = '0px';
|
||||||
|
document.getElementById("scatter").style.visibility = 'hidden';
|
||||||
|
document.getElementById("scatter").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("chart").style.height = '0px';
|
||||||
|
document.getElementById("chart").style.visibility = 'hidden';
|
||||||
|
document.getElementById("chart").style.display = 'none';
|
||||||
|
|
||||||
|
} else if (config.spectrum == 'scatter'){
|
||||||
|
|
||||||
document.getElementById("waterfall-scatter-switch1").checked = false;
|
document.getElementById("waterfall-scatter-switch1").checked = false;
|
||||||
document.getElementById("waterfall-scatter-switch2").checked = true;
|
document.getElementById("waterfall-scatter-switch2").checked = true;
|
||||||
document.getElementById("scatter").style.visibility = 'visible';
|
document.getElementById("waterfall-scatter-switch3").checked = false;
|
||||||
|
|
||||||
document.getElementById("waterfall").style.visibility = 'hidden';
|
document.getElementById("waterfall").style.visibility = 'hidden';
|
||||||
document.getElementById("waterfall").style.height = '0px';
|
document.getElementById("waterfall").style.height = '0px';
|
||||||
|
document.getElementById("waterfall").style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("scatter").style.height = '100%';
|
||||||
|
document.getElementById("scatter").style.visibility = 'visible';
|
||||||
|
document.getElementById("scatter").style.display = 'block';
|
||||||
|
|
||||||
|
document.getElementById("chart").style.visibility = 'hidden';
|
||||||
|
document.getElementById("chart").style.height = '0px';
|
||||||
|
document.getElementById("chart").style.display = 'none';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
document.getElementById("waterfall-scatter-switch1").checked = false;
|
||||||
|
document.getElementById("waterfall-scatter-switch2").checked = false;
|
||||||
|
document.getElementById("waterfall-scatter-switch3").checked = true;
|
||||||
|
|
||||||
|
document.getElementById("waterfall").style.visibility = 'hidden';
|
||||||
|
document.getElementById("waterfall").style.height = '0px';
|
||||||
|
document.getElementById("waterfall").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("scatter").style.height = '0px';
|
||||||
|
document.getElementById("scatter").style.visibility = 'hidden';
|
||||||
|
document.getElementById("scatter").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("chart").style.visibility = 'visible';
|
||||||
|
document.getElementById("chart").style.height = '100%';
|
||||||
|
document.getElementById("chart").style.display = 'block';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// radio control element
|
// radio control element
|
||||||
|
@ -291,10 +413,13 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
||||||
// Create spectrum object on canvas with ID "waterfall"
|
// Create spectrum object on canvas with ID "waterfall"
|
||||||
global.spectrum = new Spectrum(
|
global.spectrum = new Spectrum(
|
||||||
"waterfall", {
|
"waterfall", {
|
||||||
spectrumPercent: 0
|
spectrumPercent: 0,
|
||||||
|
wf_rows: 192 //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Set waterfalltheme
|
||||||
|
document.getElementById("wftheme_selector").value = config.wftheme;
|
||||||
|
spectrum.setColorMap(config.wftheme);
|
||||||
|
|
||||||
// on click radio control toggle view
|
// on click radio control toggle view
|
||||||
// disabled
|
// disabled
|
||||||
|
@ -690,21 +815,55 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
||||||
// on click waterfall scatter toggle view
|
// on click waterfall scatter toggle view
|
||||||
// waterfall
|
// waterfall
|
||||||
document.getElementById("waterfall-scatter-switch1").addEventListener("click", () => {
|
document.getElementById("waterfall-scatter-switch1").addEventListener("click", () => {
|
||||||
|
document.getElementById("chart").style.visibility = 'hidden';
|
||||||
|
document.getElementById("chart").style.display = 'none';
|
||||||
|
document.getElementById("chart").style.height = '0px';
|
||||||
|
|
||||||
|
document.getElementById("scatter").style.height = '0px';
|
||||||
|
document.getElementById("scatter").style.display = 'none';
|
||||||
document.getElementById("scatter").style.visibility = 'hidden';
|
document.getElementById("scatter").style.visibility = 'hidden';
|
||||||
|
|
||||||
|
document.getElementById("waterfall").style.display = 'block';
|
||||||
document.getElementById("waterfall").style.visibility = 'visible';
|
document.getElementById("waterfall").style.visibility = 'visible';
|
||||||
document.getElementById("waterfall").style.height = '100%';
|
document.getElementById("waterfall").style.height = '100%';
|
||||||
|
|
||||||
config.spectrum = 'waterfall';
|
config.spectrum = 'waterfall';
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
});
|
});
|
||||||
// scatter
|
// scatter
|
||||||
document.getElementById("waterfall-scatter-switch2").addEventListener("click", () => {
|
document.getElementById("waterfall-scatter-switch2").addEventListener("click", () => {
|
||||||
|
document.getElementById("scatter").style.display = 'block';
|
||||||
document.getElementById("scatter").style.visibility = 'visible';
|
document.getElementById("scatter").style.visibility = 'visible';
|
||||||
|
document.getElementById("scatter").style.height = '100%';
|
||||||
|
|
||||||
document.getElementById("waterfall").style.visibility = 'hidden';
|
document.getElementById("waterfall").style.visibility = 'hidden';
|
||||||
document.getElementById("waterfall").style.height = '0px';
|
document.getElementById("waterfall").style.height = '0px';
|
||||||
|
document.getElementById("waterfall").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("chart").style.visibility = 'hidden';
|
||||||
|
document.getElementById("chart").style.height = '0px';
|
||||||
|
document.getElementById("chart").style.display = 'none';
|
||||||
|
|
||||||
config.spectrum = 'scatter';
|
config.spectrum = 'scatter';
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
});
|
});
|
||||||
|
// chart
|
||||||
|
document.getElementById("waterfall-scatter-switch3").addEventListener("click", () => {
|
||||||
|
document.getElementById("waterfall").style.visibility = 'hidden';
|
||||||
|
document.getElementById("waterfall").style.height = '0px';
|
||||||
|
document.getElementById("waterfall").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("scatter").style.height = '0px';
|
||||||
|
document.getElementById("scatter").style.visibility = 'hidden';
|
||||||
|
document.getElementById("scatter").style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById("chart").style.height = '100%';
|
||||||
|
document.getElementById("chart").style.display = 'block';
|
||||||
|
document.getElementById("chart").style.visibility = 'visible';
|
||||||
|
|
||||||
|
config.spectrum = 'chart';
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// on click remote tnc toggle view
|
// on click remote tnc toggle view
|
||||||
|
@ -798,12 +957,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
||||||
ipcRenderer.send('run-tnc-command', Data);
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
});
|
});
|
||||||
// saveMyCall button clicked
|
// saveMyCall button clicked
|
||||||
document.getElementById("saveMyCall").addEventListener("click", () => {
|
document.getElementById("myCall").addEventListener("input", () => {
|
||||||
callsign = document.getElementById("myCall").value;
|
callsign = document.getElementById("myCall").value;
|
||||||
ssid = document.getElementById("myCallSSID").value;
|
ssid = document.getElementById("myCallSSID").value;
|
||||||
callsign_ssid = callsign.toUpperCase() + '-' + ssid;
|
callsign_ssid = callsign.toUpperCase() + '-' + ssid;
|
||||||
config.mycall = callsign_ssid;
|
config.mycall = callsign_ssid;
|
||||||
|
|
||||||
// split document title by looking for Call then split and update it
|
// split document title by looking for Call then split and update it
|
||||||
var documentTitle = document.title.split('Call:')
|
var documentTitle = document.title.split('Call:')
|
||||||
document.title = documentTitle[0] + 'Call: ' + callsign_ssid;
|
document.title = documentTitle[0] + 'Call: ' + callsign_ssid;
|
||||||
|
@ -813,7 +971,7 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// saveMyGrid button clicked
|
// saveMyGrid button clicked
|
||||||
document.getElementById("saveMyGrid").addEventListener("click", () => {
|
document.getElementById("myGrid").addEventListener("input", () => {
|
||||||
grid = document.getElementById("myGrid").value;
|
grid = document.getElementById("myGrid").value;
|
||||||
config.mygrid = grid;
|
config.mygrid = grid;
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
|
@ -967,6 +1125,14 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Waterfall theme selector changed
|
||||||
|
document.getElementById("wftheme_selector").addEventListener("change", () => {
|
||||||
|
var wftheme = document.getElementById("wftheme_selector").value;
|
||||||
|
spectrum.setColorMap(wftheme);
|
||||||
|
config.wftheme = wftheme;
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
|
});
|
||||||
|
|
||||||
// Update channel selector clicked
|
// Update channel selector clicked
|
||||||
document.getElementById("update_channel_selector").addEventListener("click", () => {
|
document.getElementById("update_channel_selector").addEventListener("click", () => {
|
||||||
config.update_channel = document.getElementById("update_channel_selector").value;
|
config.update_channel = document.getElementById("update_channel_selector").value;
|
||||||
|
@ -996,6 +1162,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
||||||
sock.stopBeacon();
|
sock.stopBeacon();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Explorer button clicked
|
||||||
|
document.getElementById("openExplorer").addEventListener("click", () => {
|
||||||
|
shell.openExternal('https://explorer.freedata.app/?myCall=' + document.getElementById("myCall").value);
|
||||||
|
});
|
||||||
|
|
||||||
// startTNC button clicked
|
// startTNC button clicked
|
||||||
document.getElementById("startTNC").addEventListener("click", () => {
|
document.getElementById("startTNC").addEventListener("click", () => {
|
||||||
|
|
||||||
|
@ -1314,28 +1485,24 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof(arg.mycallsign) !== 'undefined') {
|
||||||
// TOE TIME OF EXECUTION --> How many time needs a command to be executed until data arrives
|
// split document title by looking for Call then split and update it
|
||||||
// deactivated this feature, beacuse its useless at this time. maybe it is getting more interesting, if we are working via network
|
var documentTitle = document.title.split('Call:')
|
||||||
// but for this we need to find a nice place for this on the screen
|
document.title = documentTitle[0] + 'Call: ' + arg.mycallsign;
|
||||||
/*
|
}
|
||||||
if (typeof(arg.toe) == 'undefined') {
|
|
||||||
var toe = 0
|
// update mygrid information with data from tnc
|
||||||
} else {
|
if (typeof(arg.mygrid) !== 'undefined') {
|
||||||
var toe = arg.toe
|
document.getElementById("myGrid").value = arg.mygrid;
|
||||||
}
|
}
|
||||||
document.getElementById("toe").innerHTML = toe + ' ms'
|
|
||||||
*/
|
|
||||||
|
|
||||||
// DATA STATE
|
// DATA STATE
|
||||||
global.rxBufferLengthTnc = arg.rx_buffer_length
|
global.rxBufferLengthTnc = arg.rx_buffer_length
|
||||||
|
|
||||||
// SCATTER DIAGRAM PLOTTING
|
|
||||||
//global.myChart.destroy();
|
|
||||||
|
|
||||||
//console.log(arg.scatter.length)
|
// START OF SCATTER CHART
|
||||||
|
|
||||||
const config = {
|
const scatterConfig = {
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: false,
|
display: false,
|
||||||
|
@ -1389,16 +1556,12 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var scatterData = arg.scatter
|
||||||
|
var newScatterData = {
|
||||||
|
|
||||||
|
|
||||||
var data = arg.scatter
|
|
||||||
var newdata = {
|
|
||||||
datasets: [{
|
datasets: [{
|
||||||
//label: 'constellation diagram',
|
//label: 'constellation diagram',
|
||||||
data: data,
|
data: scatterData,
|
||||||
options: config,
|
options: scatterConfig,
|
||||||
backgroundColor: 'rgb(255, 99, 132)'
|
backgroundColor: 'rgb(255, 99, 132)'
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
@ -1408,24 +1571,121 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
} else {
|
} else {
|
||||||
var scatterSize = arg.scatter.length;
|
var scatterSize = arg.scatter.length;
|
||||||
}
|
}
|
||||||
if (global.data != newdata && scatterSize > 0) {
|
|
||||||
try {
|
|
||||||
global.myChart.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
// myChart not yet created
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
global.data = newdata;
|
if (global.scatterData != newScatterData && scatterSize > 0) {
|
||||||
|
global.scatterData = newScatterData;
|
||||||
|
|
||||||
|
if (typeof(global.scatterChart) == 'undefined') {
|
||||||
var ctx = document.getElementById('scatter').getContext('2d');
|
var scatterCtx = document.getElementById('scatter').getContext('2d');
|
||||||
global.myChart = new Chart(ctx, {
|
global.scatterChart = new Chart(scatterCtx, {
|
||||||
type: 'scatter',
|
type: 'scatter',
|
||||||
data: global.data,
|
data: global.scatterData,
|
||||||
options: config
|
options: scatterConfig
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
global.scatterChart.data = global.scatterData;
|
||||||
|
global.scatterChart.update();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// END OF SCATTER CHART
|
||||||
|
|
||||||
|
// START OF SPEED CHART
|
||||||
|
|
||||||
|
var speedDataTime = []
|
||||||
|
|
||||||
|
if (typeof(arg.speed_list) == 'undefined') {
|
||||||
|
var speed_listSize = 0;
|
||||||
|
} else {
|
||||||
|
var speed_listSize = arg.speed_list.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0; i < speed_listSize; i++) {
|
||||||
|
var timestamp = arg.speed_list[i].timestamp * 1000
|
||||||
|
var h = new Date(timestamp).getHours();
|
||||||
|
var m = new Date(timestamp).getMinutes();
|
||||||
|
var s = new Date(timestamp).getSeconds();
|
||||||
|
var time = h + ':' + m + ':' + s;
|
||||||
|
speedDataTime.push(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
var speedDataBpm = []
|
||||||
|
for (var i=0; i < speed_listSize; i++) {
|
||||||
|
speedDataBpm.push(arg.speed_list[i].bpm)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var speedDataSnr = []
|
||||||
|
for (var i=0; i < speed_listSize; i++) {
|
||||||
|
speedDataSnr.push(arg.speed_list[i].snr)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var speedChartConfig = {
|
||||||
|
type: 'line',
|
||||||
|
};
|
||||||
|
|
||||||
|
var newSpeedData = {
|
||||||
|
labels: speedDataTime,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
label: 'SNR[dB]',
|
||||||
|
data: speedDataSnr,
|
||||||
|
borderColor: 'rgb(255, 99, 132, 1.0)',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
|
order: 1,
|
||||||
|
yAxisID: 'SNR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Speed[bpm]',
|
||||||
|
data: speedDataBpm,
|
||||||
|
borderColor: 'rgb(120, 100, 120, 1.0)',
|
||||||
|
backgroundColor: 'rgba(120, 100, 120, 0.2)',
|
||||||
|
order: 0,
|
||||||
|
yAxisID: 'SPEED',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var speedChartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
animations: true,
|
||||||
|
cubicInterpolationMode: 'monotone',
|
||||||
|
tension: 0.4,
|
||||||
|
scales: {
|
||||||
|
SNR:{
|
||||||
|
type: 'linear',
|
||||||
|
ticks: { beginAtZero: true, color: 'rgb(255, 99, 132)' },
|
||||||
|
position: 'right',
|
||||||
|
},
|
||||||
|
SPEED :{
|
||||||
|
type: 'linear',
|
||||||
|
ticks: { beginAtZero: true, color: 'rgb(120, 100, 120)' },
|
||||||
|
position: 'left',
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||||
|
},
|
||||||
|
},
|
||||||
|
x: { ticks: { beginAtZero: true } },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(global.speedChart) == 'undefined') {
|
||||||
|
var speedCtx = document.getElementById('chart').getContext('2d');
|
||||||
|
global.speedChart = new Chart(speedCtx, {
|
||||||
|
data: newSpeedData,
|
||||||
|
options: speedChartOptions
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(speedDataSnr.length > 0){
|
||||||
|
global.speedChart.data = newSpeedData;
|
||||||
|
global.speedChart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// END OF SPEED CHART
|
||||||
|
|
||||||
// PTT STATE
|
// PTT STATE
|
||||||
if (arg.ptt_state == 'True') {
|
if (arg.ptt_state == 'True') {
|
||||||
|
@ -1436,6 +1696,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
document.getElementById("ptt_state").className = "btn btn-sm btn-secondary";
|
document.getElementById("ptt_state").className = "btn btn-sm btn-secondary";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AUDIO RECORDING
|
||||||
|
if (arg.audio_recording == 'True') {
|
||||||
|
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||||
|
document.getElementById("startStopRecording").innerHTML = "Stop Rec"
|
||||||
|
} else if (arg.ptt_state == 'False') {
|
||||||
|
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||||
|
document.getElementById("startStopRecording").innerHTML = "Start Rec"
|
||||||
|
} else {
|
||||||
|
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||||
|
document.getElementById("startStopRecording").innerHTML = "Start Rec"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// CHANNEL BUSY STATE
|
// CHANNEL BUSY STATE
|
||||||
if (arg.channel_busy == 'True') {
|
if (arg.channel_busy == 'True') {
|
||||||
document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
|
document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
|
||||||
|
@ -1532,7 +1808,10 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SET FREQUENCY
|
// SET FREQUENCY
|
||||||
document.getElementById("frequency").innerHTML = arg.frequency;
|
// https://stackoverflow.com/a/2901298
|
||||||
|
var freq = arg.frequency.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
document.getElementById("frequency").innerHTML = freq;
|
||||||
|
//document.getElementById("newFrequency").value = arg.frequency;
|
||||||
|
|
||||||
// SET MODE
|
// SET MODE
|
||||||
document.getElementById("mode").innerHTML = arg.mode;
|
document.getElementById("mode").innerHTML = arg.mode;
|
||||||
|
@ -1556,6 +1835,29 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
}
|
}
|
||||||
document.getElementById("bytes_per_min_compressed").innerHTML = arq_bytes_per_minute_compressed;
|
document.getElementById("bytes_per_min_compressed").innerHTML = arq_bytes_per_minute_compressed;
|
||||||
|
|
||||||
|
// SET TIME LEFT UNTIL FINIHED
|
||||||
|
if (typeof(arg.arq_seconds_until_finish) == 'undefined') {
|
||||||
|
var time_left = 0;
|
||||||
|
} else {
|
||||||
|
var arq_seconds_until_finish = arg.arq_seconds_until_finish
|
||||||
|
var hours = Math.floor(arq_seconds_until_finish / 3600);
|
||||||
|
var minutes = Math.floor((arq_seconds_until_finish % 3600) / 60 );
|
||||||
|
var seconds = arq_seconds_until_finish % 60;
|
||||||
|
|
||||||
|
if(hours < 0) {
|
||||||
|
hours = 0;
|
||||||
|
}
|
||||||
|
if(minutes < 0) {
|
||||||
|
minutes = 0;
|
||||||
|
}
|
||||||
|
if(seconds < 0) {
|
||||||
|
seconds = 0;
|
||||||
|
}
|
||||||
|
var time_left = "time left: ~ "+ minutes + "min" + " " + seconds + "s";
|
||||||
|
}
|
||||||
|
document.getElementById("transmission_timeleft").innerHTML = time_left;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// SET SPEED LEVEL
|
// SET SPEED LEVEL
|
||||||
|
|
||||||
|
@ -1577,6 +1879,7 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// SET TOTAL BYTES
|
// SET TOTAL BYTES
|
||||||
if (typeof(arg.total_bytes) == 'undefined') {
|
if (typeof(arg.total_bytes) == 'undefined') {
|
||||||
var total_bytes = 0;
|
var total_bytes = 0;
|
||||||
|
@ -2070,10 +2373,20 @@ ipcRenderer.on('run-tnc-command', (event, arg) => {
|
||||||
if (arg.command == 'set_tx_audio_level') {
|
if (arg.command == 'set_tx_audio_level') {
|
||||||
sock.setTxAudioLevel(arg.tx_audio_level);
|
sock.setTxAudioLevel(arg.tx_audio_level);
|
||||||
}
|
}
|
||||||
|
if (arg.command == 'record_audio') {
|
||||||
|
sock.record_audio();
|
||||||
|
}
|
||||||
if (arg.command == 'send_test_frame') {
|
if (arg.command == 'send_test_frame') {
|
||||||
sock.sendTestFrame();
|
sock.sendTestFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg.command == 'frequency') {
|
||||||
|
sock.set_frequency(arg.frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.command == 'mode') {
|
||||||
|
sock.set_mode(arg.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
53
gui/sock.js
53
gui/sock.js
|
@ -19,7 +19,7 @@ var client = new net.Socket();
|
||||||
var socketchunk = ''; // Current message, per connection.
|
var socketchunk = ''; // Current message, per connection.
|
||||||
|
|
||||||
// split character
|
// split character
|
||||||
const split_char = '\0;'
|
const split_char = '\0;\1;'
|
||||||
|
|
||||||
// globals for getting new data only if available so we are saving bandwidth
|
// globals for getting new data only if available so we are saving bandwidth
|
||||||
var rxBufferLengthTnc = 0
|
var rxBufferLengthTnc = 0
|
||||||
|
@ -195,6 +195,8 @@ client.on('data', function(socketdata) {
|
||||||
rxMsgBufferLengthTnc = data['rx_msg_buffer_length']
|
rxMsgBufferLengthTnc = data['rx_msg_buffer_length']
|
||||||
|
|
||||||
let Data = {
|
let Data = {
|
||||||
|
mycallsign: data['mycallsign'],
|
||||||
|
mygrid: data['mygrid'],
|
||||||
ptt_state: data['ptt_state'],
|
ptt_state: data['ptt_state'],
|
||||||
busy_state: data['tnc_state'],
|
busy_state: data['tnc_state'],
|
||||||
arq_state: data['arq_state'],
|
arq_state: data['arq_state'],
|
||||||
|
@ -220,12 +222,17 @@ client.on('data', function(socketdata) {
|
||||||
arq_rx_n_current_arq_frame: data['arq_rx_n_current_arq_frame'],
|
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_n_arq_frames_per_data_frame: data['arq_n_arq_frames_per_data_frame'],
|
||||||
arq_bytes_per_minute: data['arq_bytes_per_minute'],
|
arq_bytes_per_minute: data['arq_bytes_per_minute'],
|
||||||
|
arq_seconds_until_finish: data['arq_seconds_until_finish'],
|
||||||
arq_compression_factor: data['arq_compression_factor'],
|
arq_compression_factor: data['arq_compression_factor'],
|
||||||
total_bytes: data['total_bytes'],
|
total_bytes: data['total_bytes'],
|
||||||
arq_transmission_percent: data['arq_transmission_percent'],
|
arq_transmission_percent: data['arq_transmission_percent'],
|
||||||
stations: data['stations'],
|
stations: data['stations'],
|
||||||
beacon_state: data['beacon_state'],
|
beacon_state: data['beacon_state'],
|
||||||
hamlib_status: data['hamlib_status'],
|
hamlib_status: data['hamlib_status'],
|
||||||
|
listen: data['listen'],
|
||||||
|
audio_recording: data['audio_recording'],
|
||||||
|
speed_list: data['speed_list'],
|
||||||
|
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.send('request-update-tnc-state', Data);
|
ipcRenderer.send('request-update-tnc-state', Data);
|
||||||
|
@ -514,20 +521,25 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data,
|
||||||
|
|
||||||
// Send Message
|
// Send Message
|
||||||
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) {
|
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) {
|
||||||
socketLog.info(data)
|
//socketLog.info(data)
|
||||||
|
|
||||||
|
// Disabled this here
|
||||||
// convert message to plain utf8 because of unicode emojis
|
// convert message to plain utf8 because of unicode emojis
|
||||||
data = utf8.encode(data)
|
//data = utf8.encode(data)
|
||||||
socketLog.info(data)
|
|
||||||
|
//socketLog.info(data)
|
||||||
|
|
||||||
|
|
||||||
var datatype = "m"
|
var datatype = "m"
|
||||||
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
||||||
socketLog.info(data)
|
//socketLog.info(data)
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
console.log("CHECKSUM" + checksum)
|
||||||
|
//socketLog.info(btoa(data))
|
||||||
socketLog.info(btoa(data))
|
|
||||||
data = 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_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}'
|
||||||
command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}'
|
command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}'
|
||||||
socketLog.info(command)
|
socketLog.info(command)
|
||||||
|
@ -583,7 +595,24 @@ exports.sendTestFrame = function() {
|
||||||
writeTncCommand(command)
|
writeTncCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RECORD AUDIO
|
||||||
|
exports.record_audio = function() {
|
||||||
|
command = '{"type" : "set", "command" : "record_audio"}'
|
||||||
|
writeTncCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET FREQUENCY
|
||||||
|
exports.set_frequency = function(frequency) {
|
||||||
|
command = '{"type" : "set", "command" : "frequency", "frequency": '+ frequency +'}'
|
||||||
|
writeTncCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET MODE
|
||||||
|
exports.set_mode = function(mode) {
|
||||||
|
command = '{"type" : "set", "command" : "mode", "mode": "'+ mode +'"}'
|
||||||
|
console.log(command)
|
||||||
|
writeTncCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
|
@ -602,3 +631,11 @@ ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
||||||
connectTNC();
|
connectTNC();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/50579690
|
||||||
|
// crc32 calculation
|
||||||
|
//console.log(crc32('abc'));
|
||||||
|
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||||
|
var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<button class="btn btn-sm btn-danger" id="stop_transmission_connection" type="button"> <i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i> STOP </button>
|
<button class="btn btn-sm btn-danger" id="stop_transmission_connection" type="button"> <i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i> STOP </button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<button class="btn btn-sm btn-primary me-4 position-relative" id="openExplorer" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View explorer map"> <strong>Explorer</strong> <i class="bi bi-pin-map-fill" style="font-size: 1rem; color: white;"></i></button>
|
||||||
<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!"> <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">.</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-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!"> <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">.</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!">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
|
@ -731,7 +732,6 @@
|
||||||
<option value="14">14</option>
|
<option value="14">14</option>
|
||||||
<option value="15">15</option>
|
<option value="15">15</option>
|
||||||
</select>
|
</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>
|
</div>
|
||||||
<div class="col-md-auto">
|
<div class="col-md-auto">
|
||||||
|
@ -739,7 +739,6 @@
|
||||||
<i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i>
|
<i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i>
|
||||||
</span>
|
</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">
|
<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-sm btn-success" id="saveMyGrid" type="button"> <i class="bi bi-check2" style="font-size: 1rem; color: white;"></i> </button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -770,6 +769,8 @@
|
||||||
<div class="card text-dark mb-1">
|
<div class="card text-dark mb-1">
|
||||||
<div class="card-header p-1"><i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i> <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>
|
||||||
<button type="button" id="audioModalButton" data-bs-toggle="modal" data-bs-target="#audioModal" class="btn btn-sm btn-secondary">Tune</button>
|
<button type="button" id="audioModalButton" data-bs-toggle="modal" data-bs-target="#audioModal" class="btn btn-sm btn-secondary">Tune</button>
|
||||||
|
<button type="button" id="startStopRecording" class="btn btn-sm btn-danger">Rec</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
<div class="progress mb-0" style="height: 15px;">
|
<div class="progress mb-0" style="height: 15px;">
|
||||||
|
@ -837,37 +838,19 @@
|
||||||
<div class="card-header p-1">
|
<div class="card-header p-1">
|
||||||
<div class="btn-group btn-group-sm" role="group" aria-label="waterfall-scatter-switch toggle button group">
|
<div class="btn-group btn-group-sm" role="group" aria-label="waterfall-scatter-switch toggle button group">
|
||||||
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch1" autocomplete="off" checked>
|
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch1" autocomplete="off" checked>
|
||||||
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch1"><strong>WATERFALL</strong> </label>
|
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch1"><strong><i class="bi bi-water"></i></strong> </label>
|
||||||
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch2" autocomplete="off">
|
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch2" autocomplete="off">
|
||||||
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch2"><strong>SCATTER</strong> </label>
|
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch2"><strong><i class="bi bi-border-outer"></i></strong> </label>
|
||||||
|
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch3" autocomplete="off">
|
||||||
|
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch3"><strong><i class="bi bi-graph-up-arrow"></i></strong> </label>
|
||||||
</div>
|
</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>
|
<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>
|
||||||
<div class="card-body p-1" style="height: 200px">
|
<div class="card-body p-1" style="height: 200px">
|
||||||
<!-- TEST FOR WATERFALL OVERLAY
|
|
||||||
<div class="opacity-100 w-100 h-100 p-0 m-0 position-absolute" style="height: 190px;z-index: 10">
|
|
||||||
<div class="row m-0 p-0 w-100 h-100">
|
|
||||||
<div class="col m-0 p-0 col-3 ">
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
<div class="col border border-danger m-0 p-0 col-2">
|
|
||||||
1800Hz
|
|
||||||
</div>
|
|
||||||
<div class="col border border-danger m-0 p-0" style="width: 190px;">
|
|
||||||
500Hz
|
|
||||||
</div>
|
|
||||||
<div class="col border border-danger m-0 p-0 col-2">
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
<div class="col m-0 p-0 col-3">
|
|
||||||
-
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<!--278px-->
|
<!--278px-->
|
||||||
<canvas id="waterfall" style="position: relative; z-index: 2;"></canvas>
|
<canvas id="waterfall" style="position: relative; z-index: 2; transform: translateZ(0);"></canvas>
|
||||||
<canvas id="scatter" style="position: relative; z-index: 1;"></canvas>
|
<canvas id="scatter" style="position: relative; z-index: 1; transform: translateZ(0);"></canvas>
|
||||||
|
<canvas id="chart" style="position: relative; z-index: 1; transform: translateZ(0);"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1066,28 +1049,64 @@
|
||||||
<nav class="navbar fixed-bottom navbar-light bg-light">
|
<nav class="navbar fixed-bottom navbar-light bg-light">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-toolbar" role="toolbar">
|
||||||
<div class="btn-group btn-group-sm me-2" role="group">
|
<div class="btn-group btn-group-sm me-1" role="group">
|
||||||
<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>
|
<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>
|
||||||
<div class="btn-group btn-group-sm me-2" role="group">
|
<div class="btn-group btn-group-sm me-1" role="group">
|
||||||
<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>
|
<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>
|
||||||
<div class="btn-group btn-group-sm me-2" role="group">
|
<div class="btn-group btn-group-sm me-1" role="group">
|
||||||
<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>
|
<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>
|
||||||
<div class="btn-group btn-group-sm me-2" role="group">
|
<div class="btn-group btn-group-sm me-1" 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>
|
<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>
|
||||||
|
|
||||||
<div class="btn-group btn-group-sm me-2" role="group">
|
<div class="btn-group btn-group-sm me-1" role="group">
|
||||||
<button class="btn btn-sm btn-secondary" id="rigctld_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"> <i class="bi bi-usb-symbol" style="font-size: 0.8rem; color: white;"></i> </button>
|
<button class="btn btn-sm btn-secondary" id="rigctld_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"> <i class="bi bi-usb-symbol" style="font-size: 0.8rem; color: white;"></i> </button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-fluid p-0" style="width:15rem">
|
<div class="container-fluid p-0" style="width:20rem">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<!--<span class="input-group-text" id="basic-addon1"><strong>Freq</strong></span>--><span class="input-group-text" id="frequency">---</span>
|
|
||||||
<!--<span class="input-group-text" id="basic-addon1"><strong>Mode</strong></span>--><span class="input-group-text" id="mode">---</span>
|
<div class="btn-group dropup me-1">
|
||||||
<!--<span class="input-group-text" id="basic-addon1"><strong>BW</strong></span>--><span class="input-group-text" id="bandwidth">---</span> </div>
|
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="frequency">
|
||||||
|
---
|
||||||
|
</button>
|
||||||
|
<form class="dropdown-menu p-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control" style="max-width: 6rem;" placeholder="7063000" pattern="[0-9]*" id="newFrequency" maxlength="11" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||||
|
<span class="input-group-text">Hz</span>
|
||||||
|
<button class="btn btn-sm btn-success" id="saveFrequency" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="save frequency"> <i class="bi bi-check-lg" style="font-size: 0.8rem; color: white;"></i> </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group dropup me-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="mode">
|
||||||
|
---
|
||||||
|
</button>
|
||||||
|
<form class="dropdown-menu p-2">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set FM" id="saveModeFM">FM</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set AM" type="button" id="saveModeAM">AM</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set LSB" type="button" id="saveModeLSB">LSB</button>
|
||||||
|
<hr>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set USB" type="button" id="saveModeUSB">USB</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set PKTUSB" type="button" id="saveModePKTUSB">PKTUSB</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group dropup">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="bandwidth">
|
||||||
|
---
|
||||||
|
</button>
|
||||||
|
<form class="dropdown-menu p-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
...soon...
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-fluid p-0" style="width:12rem">
|
<div class="container-fluid p-0" style="width:12rem">
|
||||||
<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">
|
<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">
|
||||||
|
@ -1100,6 +1119,7 @@
|
||||||
<div class="progress" style="height: 30px;">
|
<div class="progress" style="height: 30px;">
|
||||||
<div class="progress-bar progress-bar-striped bg-primary" id="transmission_progress" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar progress-bar-striped bg-primary" id="transmission_progress" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
<!--<p class="justify-content-center d-flex position-absolute w-100">PROGRESS</p>-->
|
<!--<p class="justify-content-center d-flex position-absolute w-100">PROGRESS</p>-->
|
||||||
|
<p class="justify-content-center mt-2 d-flex position-absolute w-100" id="transmission_timeleft">---</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1107,8 +1127,8 @@
|
||||||
<!-- bootstrap -->
|
<!-- bootstrap -->
|
||||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<!-- chart.js -->
|
<!-- chart.js -->
|
||||||
<script src="../node_modules/chart.js/dist/chart.min.js"></script>
|
<script src="../node_modules/chart.js/dist/chart.umd.js"></script>
|
||||||
<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>
|
<!--<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>-->
|
||||||
<!--<script src="../ui.js"></script>-->
|
<!--<script src="../ui.js"></script>-->
|
||||||
<!-- WATERFALL -->
|
<!-- WATERFALL -->
|
||||||
<script src="waterfall/colormap.js"></script>
|
<script src="waterfall/colormap.js"></script>
|
||||||
|
@ -1172,6 +1192,17 @@
|
||||||
<option value="zephyr">Zephyr</option>
|
<option value="zephyr">Zephyr</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Waterfall Theme</span>
|
||||||
|
<select class="form-select form-select-sm w-50" id="wftheme_selector">
|
||||||
|
<option value="2">Default</option>
|
||||||
|
<option value="0">Turbo</option>
|
||||||
|
<option value="1">Fosphor</option>
|
||||||
|
<option value="3">Inferno</option>
|
||||||
|
<option value="4">Magma</option>
|
||||||
|
<option value="5">Jet</option>
|
||||||
|
<option value="6">Binary</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Update channel</span>
|
<div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Update channel</span>
|
||||||
<select class="form-select form-select-sm w-50" id="update_channel_selector">
|
<select class="form-select form-select-sm w-50" id="update_channel_selector">
|
||||||
<option value="latest">stable</option>
|
<option value="latest">stable</option>
|
||||||
|
|
|
@ -8,6 +8,7 @@ body {
|
||||||
/*Progress bars with centered text*/
|
/*Progress bars with centered text*/
|
||||||
.progress {
|
.progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress span {
|
.progress span {
|
||||||
|
|
|
@ -68,7 +68,8 @@ Spectrum.prototype.drawFFT = function(bins) {
|
||||||
this.ctx.stroke();
|
this.ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
Spectrum.prototype.drawSpectrum = function(bins) {
|
//Spectrum.prototype.drawSpectrum = function(bins) {
|
||||||
|
Spectrum.prototype.drawSpectrum = function() {
|
||||||
var width = this.ctx.canvas.width;
|
var width = this.ctx.canvas.width;
|
||||||
var height = this.ctx.canvas.height;
|
var height = this.ctx.canvas.height;
|
||||||
|
|
||||||
|
@ -88,33 +89,24 @@ Spectrum.prototype.drawSpectrum = function(bins) {
|
||||||
this.ctx_wf.stroke()
|
this.ctx_wf.stroke()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 586Hz LINES
|
// 586Hz and 1700Hz LINES
|
||||||
var bandwidth = 568;
|
var linePositionLow = 121.6; //150 - bandwith/20
|
||||||
var linePositionLow = 150 - ((bandwidth/2)/10);
|
var linePositionHigh = 178.4; //150 + bandwidth/20
|
||||||
var linePositionHigh = 150 + ((bandwidth/2)/10);
|
var linePositionLow2 = 65; //150 - bandwith/20
|
||||||
|
var linePositionHigh2 = 235; //150 + bandwith/20
|
||||||
this.ctx_wf.beginPath();
|
this.ctx_wf.beginPath();
|
||||||
this.ctx_wf.moveTo(linePositionLow,0);
|
this.ctx_wf.moveTo(linePositionLow,0);
|
||||||
this.ctx_wf.lineTo(linePositionLow, height);
|
this.ctx_wf.lineTo(linePositionLow, height);
|
||||||
this.ctx_wf.moveTo(linePositionHigh,0);
|
this.ctx_wf.moveTo(linePositionHigh,0);
|
||||||
this.ctx_wf.lineTo(linePositionHigh, height);
|
this.ctx_wf.lineTo(linePositionHigh, height);
|
||||||
|
this.ctx_wf.moveTo(linePositionLow2,0);
|
||||||
|
this.ctx_wf.lineTo(linePositionLow2, height);
|
||||||
|
this.ctx_wf.moveTo(linePositionHigh2,0);
|
||||||
|
this.ctx_wf.lineTo(linePositionHigh2, height);
|
||||||
this.ctx_wf.lineWidth = 1;
|
this.ctx_wf.lineWidth = 1;
|
||||||
this.ctx_wf.strokeStyle = '#C3C3C3';
|
this.ctx_wf.strokeStyle = '#C3C3C3';
|
||||||
this.ctx_wf.stroke()
|
this.ctx_wf.stroke()
|
||||||
|
|
||||||
// 1700Hz LINES
|
|
||||||
var bandwidth = 1700;
|
|
||||||
var linePositionLow = 150 - ((bandwidth/2)/10);
|
|
||||||
var linePositionHigh = 150 + ((bandwidth/2)/10);
|
|
||||||
this.ctx_wf.beginPath();
|
|
||||||
this.ctx_wf.moveTo(linePositionLow,0);
|
|
||||||
this.ctx_wf.lineTo(linePositionLow, height);
|
|
||||||
this.ctx_wf.moveTo(linePositionHigh,0);
|
|
||||||
this.ctx_wf.lineTo(linePositionHigh, height);
|
|
||||||
this.ctx_wf.lineWidth = 1;
|
|
||||||
this.ctx_wf.strokeStyle = '#C3C3C3';
|
|
||||||
this.ctx_wf.stroke()
|
|
||||||
|
|
||||||
|
|
||||||
// ---- END OF MODIFICATION ------
|
// ---- END OF MODIFICATION ------
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +114,9 @@ Spectrum.prototype.drawSpectrum = function(bins) {
|
||||||
this.ctx.fillStyle = "white";
|
this.ctx.fillStyle = "white";
|
||||||
this.ctx.fillRect(0, 0, width, height);
|
this.ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
//Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements
|
||||||
|
return;
|
||||||
|
/*
|
||||||
// FFT averaging
|
// FFT averaging
|
||||||
if (this.averaging > 0) {
|
if (this.averaging > 0) {
|
||||||
if (!this.binsAverage || this.binsAverage.length != bins.length) {
|
if (!this.binsAverage || this.binsAverage.length != bins.length) {
|
||||||
|
@ -174,6 +169,12 @@ Spectrum.prototype.drawSpectrum = function(bins) {
|
||||||
|
|
||||||
// Copy axes from offscreen canvas
|
// Copy axes from offscreen canvas
|
||||||
this.ctx.drawImage(this.ctx_axes.canvas, 0, 0);
|
this.ctx.drawImage(this.ctx_axes.canvas, 0, 0);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allow setting colormap
|
||||||
|
Spectrum.prototype.setColorMap = function(index) {
|
||||||
|
this.colormap = colormaps[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
Spectrum.prototype.updateAxes = function() {
|
Spectrum.prototype.updateAxes = function() {
|
||||||
|
@ -242,7 +243,8 @@ Spectrum.prototype.addData = function(data) {
|
||||||
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
|
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
|
||||||
this.imagedata = this.ctx_wf.createImageData(data.length, 1);
|
this.imagedata = this.ctx_wf.createImageData(data.length, 1);
|
||||||
}
|
}
|
||||||
this.drawSpectrum(data);
|
//this.drawSpectrum(data);
|
||||||
|
this.drawSpectrum();
|
||||||
this.addWaterfallRow(data);
|
this.addWaterfallRow(data);
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ sounddevice
|
||||||
structlog
|
structlog
|
||||||
ujson
|
ujson
|
||||||
requests
|
requests
|
||||||
|
chardet
|
||||||
colorama
|
colorama
|
||||||
ordered-set
|
ordered-set
|
||||||
nuitka
|
nuitka
|
||||||
|
@ -17,9 +18,9 @@ autopep8
|
||||||
black
|
black
|
||||||
isort
|
isort
|
||||||
pycodestyle
|
pycodestyle
|
||||||
pyinstaller
|
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-cover
|
pytest-cover
|
||||||
pytest-coverage
|
pytest-coverage
|
||||||
pytest-rerunfailures
|
pytest-rerunfailures
|
||||||
|
pick
|
|
@ -93,7 +93,7 @@ def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
|
||||||
return t_create_frame(223, mycall, dxcall)
|
return t_create_frame(223, mycall, dxcall)
|
||||||
|
|
||||||
|
|
||||||
def t_create_session_close(session_id: bytes) -> bytearray:
|
def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray:
|
||||||
"""
|
"""
|
||||||
Generate the session_close frame.
|
Generate the session_close frame.
|
||||||
|
|
||||||
|
@ -102,10 +102,16 @@ def t_create_session_close(session_id: bytes) -> bytearray:
|
||||||
:return: Bytearray of the requested frame
|
:return: Bytearray of the requested frame
|
||||||
:rtype: bytearray
|
:rtype: bytearray
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||||
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
||||||
|
|
||||||
# return t_create_frame(223, mycall, dxcall)
|
# return t_create_frame(223, mycall, dxcall)
|
||||||
frame = bytearray(14)
|
frame = bytearray(14)
|
||||||
frame[:1] = bytes([223])
|
frame[:1] = bytes([223])
|
||||||
frame[1:2] = session_id
|
frame[1:2] = session_id
|
||||||
|
frame[2:5] = dxcallsign_crc
|
||||||
|
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
@ -183,7 +189,7 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
|
||||||
wrong_session = np.random.bytes(1)
|
wrong_session = np.random.bytes(1)
|
||||||
while wrong_session == open_session:
|
while wrong_session == open_session:
|
||||||
wrong_session = np.random.bytes(1)
|
wrong_session = np.random.bytes(1)
|
||||||
close_frame = t_create_session_close(wrong_session)
|
close_frame = t_create_session_close(wrong_session, dxcall)
|
||||||
print_frame(close_frame)
|
print_frame(close_frame)
|
||||||
|
|
||||||
# assert (
|
# assert (
|
||||||
|
@ -254,7 +260,10 @@ def t_valid_disconnect(mycall: str, dxcall: str):
|
||||||
# Create packet to be 'received' by this station.
|
# Create packet to be 'received' by this station.
|
||||||
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
|
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
|
||||||
open_session = create_frame[1:2]
|
open_session = create_frame[1:2]
|
||||||
close_frame = t_create_session_close(open_session)
|
print(dxcall)
|
||||||
|
print("#####################################################")
|
||||||
|
close_frame = t_create_session_close(open_session, mycall)
|
||||||
|
print(close_frame[2:5])
|
||||||
print_frame(close_frame)
|
print_frame(close_frame)
|
||||||
tnc.received_session_close(close_frame)
|
tnc.received_session_close(close_frame)
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,6 @@ def t_datac0_2(
|
||||||
assert item in str(
|
assert item in str(
|
||||||
sock.SOCKET_QUEUE.queue
|
sock.SOCKET_QUEUE.queue
|
||||||
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
|
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||||
|
# TODO: Not sure why we need this for every test run
|
||||||
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||||
log.warning("station2: Exiting!")
|
log.warning("station2: Exiting!")
|
||||||
|
|
|
@ -287,7 +287,10 @@ def t_datac0_2(
|
||||||
|
|
||||||
# Allow enough time for this side to receive the disconnect frame.
|
# Allow enough time for this side to receive the disconnect frame.
|
||||||
timeout = time.time() + timeout_duration
|
timeout = time.time() + timeout_duration
|
||||||
while '"arq":"session","status":"close"' not in str(sock.SOCKET_QUEUE.queue):
|
while '"arq":"session", "status":"close"' not in str(sock.SOCKET_QUEUE.queue):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if time.time() > timeout:
|
if time.time() > timeout:
|
||||||
log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue))
|
log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue))
|
||||||
break
|
break
|
||||||
|
@ -302,6 +305,6 @@ def t_datac0_2(
|
||||||
assert item not in str(
|
assert item not in str(
|
||||||
sock.SOCKET_QUEUE.queue
|
sock.SOCKET_QUEUE.queue
|
||||||
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
|
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||||
|
# TODO: Not sure why we need this for every test run
|
||||||
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||||
log.warning("station2: Exiting!")
|
log.warning("station2: Exiting!")
|
||||||
|
|
|
@ -60,16 +60,19 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
# Check if we are running in a pyinstaller environment
|
# Check if we are running in a pyinstaller environment
|
||||||
if hasattr(sys, "_MEIPASS"):
|
#if hasattr(sys, "_MEIPASS"):
|
||||||
sys.path.append(getattr(sys, "_MEIPASS"))
|
# sys.path.append(getattr(sys, "_MEIPASS"))
|
||||||
else:
|
#else:
|
||||||
sys.path.append(os.path.abspath("."))
|
sys.path.append(os.path.abspath("."))
|
||||||
|
|
||||||
log.info("[C2 ] Searching for libcodec2...")
|
log.info("[C2 ] Searching for libcodec2...")
|
||||||
if sys.platform == "linux":
|
if sys.platform == "linux":
|
||||||
files = glob.glob(r"**/*libcodec2*", recursive=True)
|
files = glob.glob(r"**/*libcodec2*", recursive=True)
|
||||||
files.append("libcodec2.so")
|
files.append("libcodec2.so")
|
||||||
elif sys.platform == "darwin":
|
elif sys.platform == "darwin":
|
||||||
|
if hasattr(sys, "_MEIPASS"):
|
||||||
|
files = glob.glob(getattr(sys, "_MEIPASS") + '/**/*libcodec2*', recursive=True)
|
||||||
|
else:
|
||||||
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
|
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
|
||||||
elif sys.platform in ["win32", "win64"]:
|
elif sys.platform in ["win32", "win64"]:
|
||||||
files = glob.glob(r"**\*libcodec2*.dll", recursive=True)
|
files = glob.glob(r"**\*libcodec2*.dll", recursive=True)
|
||||||
|
|
33
tnc/config.ini
Normal file
33
tnc/config.ini
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[NETWORK]
|
||||||
|
#network settings
|
||||||
|
tncport = 3000
|
||||||
|
|
||||||
|
[STATION]
|
||||||
|
#station settings
|
||||||
|
mycall = DJ2LS-4
|
||||||
|
mygrid = JN48cs
|
||||||
|
ssid_list = [0,1,2,3,4,5]
|
||||||
|
|
||||||
|
[AUDIO]
|
||||||
|
#audio settings
|
||||||
|
rx = hw:2,0
|
||||||
|
tx = USB Audio CODEC
|
||||||
|
txaudiolevel = 120
|
||||||
|
|
||||||
|
[RADIO]
|
||||||
|
#radio settings
|
||||||
|
radiocontrol = rigctld
|
||||||
|
rigctld_ip = 127.0.0.1
|
||||||
|
rigctld_port = 4532
|
||||||
|
|
||||||
|
[TNC]
|
||||||
|
#tnc settings
|
||||||
|
scatter = True
|
||||||
|
fft = True
|
||||||
|
narrowband = False
|
||||||
|
fmin = -50.0
|
||||||
|
fmax = 50.0
|
||||||
|
qrv = True
|
||||||
|
rxbuffersize = 16
|
||||||
|
explorer = False
|
||||||
|
|
|
@ -7,12 +7,17 @@ class CONFIG:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, configfile: str):
|
||||||
# set up logger
|
# set up logger
|
||||||
self.log = structlog.get_logger("CONFIG")
|
self.log = structlog.get_logger("CONFIG")
|
||||||
|
|
||||||
# init configparser
|
# init configparser
|
||||||
self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.config_name = configfile
|
||||||
|
|
||||||
|
except Exception:
|
||||||
self.config_name = "config.ini"
|
self.config_name = "config.ini"
|
||||||
|
|
||||||
self.log.info("[CFG] logfile init", file=self.config_name)
|
self.log.info("[CFG] logfile init", file=self.config_name)
|
||||||
|
@ -45,7 +50,8 @@ class CONFIG:
|
||||||
|
|
||||||
self.config['STATION'] = {'#Station settings': None,
|
self.config['STATION'] = {'#Station settings': None,
|
||||||
'mycall': data[1],
|
'mycall': data[1],
|
||||||
'mygrid': data[2]
|
'mygrid': data[2],
|
||||||
|
'ssid_list': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # list(data[26])
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config['AUDIO'] = {'#Audio settings': None,
|
self.config['AUDIO'] = {'#Audio settings': None,
|
||||||
|
|
|
@ -93,7 +93,7 @@ class DAEMON:
|
||||||
"[DMN] update_audio_devices: Exception gathering audio devices:",
|
"[DMN] update_audio_devices: Exception gathering audio devices:",
|
||||||
e=err1,
|
e=err1,
|
||||||
)
|
)
|
||||||
time.sleep(1)
|
threading.Event().wait(1)
|
||||||
|
|
||||||
def update_serial_devices(self):
|
def update_serial_devices(self):
|
||||||
"""
|
"""
|
||||||
|
@ -114,7 +114,7 @@ class DAEMON:
|
||||||
)
|
)
|
||||||
|
|
||||||
static.SERIAL_DEVICES = serial_devices
|
static.SERIAL_DEVICES = serial_devices
|
||||||
time.sleep(1)
|
threading.Event().wait(1)
|
||||||
except Exception as err1:
|
except Exception as err1:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
"[DMN] update_serial_devices: Exception gathering serial devices:",
|
"[DMN] update_serial_devices: Exception gathering serial devices:",
|
||||||
|
@ -156,7 +156,8 @@ class DAEMON:
|
||||||
# data[22] tx-audio-level
|
# data[22] tx-audio-level
|
||||||
# data[23] respond_to_cq
|
# data[23] respond_to_cq
|
||||||
# data[24] rx_buffer_size
|
# data[24] rx_buffer_size
|
||||||
|
# data[25] explorer
|
||||||
|
# data[26] ssid_list
|
||||||
|
|
||||||
if data[0] == "STARTTNC":
|
if data[0] == "STARTTNC":
|
||||||
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
|
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
|
||||||
|
@ -251,13 +252,36 @@ class DAEMON:
|
||||||
if data[25] == "True":
|
if data[25] == "True":
|
||||||
options.append("--explorer")
|
options.append("--explorer")
|
||||||
|
|
||||||
|
# wen want our ssid like this: --ssid 1 2 3 4
|
||||||
|
ssid_list = ""
|
||||||
|
for i in data[26]:
|
||||||
|
ssid_list += str(i) + " "
|
||||||
|
options.append("--ssid")
|
||||||
|
options.append(ssid_list)
|
||||||
|
|
||||||
|
|
||||||
# safe data to config file
|
# safe data to config file
|
||||||
config.write_entire_config(data)
|
config.write_entire_config(data)
|
||||||
|
import os, sys
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Try running tnc from binary, else run from source
|
# Try running tnc from binary, else run from source
|
||||||
# This helps running the tnc in a developer environment
|
# This helps running the tnc in a developer environment
|
||||||
try:
|
try:
|
||||||
command = []
|
command = []
|
||||||
|
|
||||||
|
if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]:
|
||||||
|
# If the application is run as a bundle, the PyInstaller bootloader
|
||||||
|
# extends the sys module by a flag frozen=True and sets the app
|
||||||
|
# path into variable _MEIPASS'.
|
||||||
|
application_path = sys._MEIPASS
|
||||||
|
command.append(application_path + '/freedata-tnc')
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
if sys.platform in ["linux", "darwin"]:
|
if sys.platform in ["linux", "darwin"]:
|
||||||
command.append("./freedata-tnc")
|
command.append("./freedata-tnc")
|
||||||
elif sys.platform in ["win32", "win64"]:
|
elif sys.platform in ["win32", "win64"]:
|
||||||
|
@ -272,6 +296,7 @@ class DAEMON:
|
||||||
except FileNotFoundError as err1:
|
except FileNotFoundError as err1:
|
||||||
self.log.info("[DMN] worker: ", e=err1)
|
self.log.info("[DMN] worker: ", e=err1)
|
||||||
command = []
|
command = []
|
||||||
|
|
||||||
if sys.platform in ["linux", "darwin"]:
|
if sys.platform in ["linux", "darwin"]:
|
||||||
command.append("python3")
|
command.append("python3")
|
||||||
elif sys.platform in ["win32", "win64"]:
|
elif sys.platform in ["win32", "win64"]:
|
||||||
|
@ -279,6 +304,7 @@ class DAEMON:
|
||||||
|
|
||||||
command.append("main.py")
|
command.append("main.py")
|
||||||
command += options
|
command += options
|
||||||
|
print(command)
|
||||||
proc = subprocess.Popen(command)
|
proc = subprocess.Popen(command)
|
||||||
atexit.register(proc.kill)
|
atexit.register(proc.kill)
|
||||||
|
|
||||||
|
@ -407,7 +433,7 @@ if __name__ == "__main__":
|
||||||
mainlog.error("[DMN] logger init error", exception=err)
|
mainlog.error("[DMN] logger init error", exception=err)
|
||||||
|
|
||||||
# init config
|
# init config
|
||||||
config = config.CONFIG()
|
config = config.CONFIG("config.ini")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT)
|
mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT)
|
||||||
|
@ -434,4 +460,4 @@ if __name__ == "__main__":
|
||||||
version=static.VERSION,
|
version=static.VERSION,
|
||||||
)
|
)
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
threading.Event().wait(1)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,15 +28,11 @@ class explorer():
|
||||||
def interval(self):
|
def interval(self):
|
||||||
while True:
|
while True:
|
||||||
self.push()
|
self.push()
|
||||||
time.sleep(self.publish_interval)
|
threading.Event().wait(self.publish_interval)
|
||||||
|
|
||||||
def push(self):
|
def push(self):
|
||||||
|
|
||||||
|
frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY
|
||||||
if static.HAMLIB_FREQUENCY is not None:
|
|
||||||
frequency = static.HAMLIB_FREQUENCY
|
|
||||||
else:
|
|
||||||
frequency = 0
|
|
||||||
band = "USB"
|
band = "USB"
|
||||||
callsign = str(static.MYCALLSIGN, "utf-8")
|
callsign = str(static.MYCALLSIGN, "utf-8")
|
||||||
gridsquare = str(static.MYGRID, "utf-8")
|
gridsquare = str(static.MYGRID, "utf-8")
|
||||||
|
@ -47,7 +43,21 @@ class explorer():
|
||||||
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon}
|
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []}
|
||||||
|
|
||||||
|
for i in static.HEARD_STATIONS:
|
||||||
|
try:
|
||||||
|
callsign = str(i[0], "UTF-8")
|
||||||
|
grid = str(i[1], "UTF-8")
|
||||||
|
timestamp = i[2]
|
||||||
|
try:
|
||||||
|
snr = i[4].split("/")[1]
|
||||||
|
except AttributeError:
|
||||||
|
snr = str(i[4])
|
||||||
|
station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr, "timestamp": timestamp})
|
||||||
|
except Exception as e:
|
||||||
|
log.debug("[EXPLORER] not publishing station", e=e)
|
||||||
|
|
||||||
station_data = json.dumps(station_data)
|
station_data = json.dumps(station_data)
|
||||||
try:
|
try:
|
||||||
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
||||||
|
|
|
@ -25,6 +25,7 @@ daemon_exe = EXE(daemon_pyz,
|
||||||
[],
|
[],
|
||||||
exclude_binaries=True,
|
exclude_binaries=True,
|
||||||
name='freedata-daemon',
|
name='freedata-daemon',
|
||||||
|
bundle_identifier='com.dj2ls.freedata-daemon',
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
strip=False,
|
strip=False,
|
||||||
|
@ -62,6 +63,7 @@ tnc_exe = EXE(tnc_pyz,
|
||||||
[],
|
[],
|
||||||
exclude_binaries=True,
|
exclude_binaries=True,
|
||||||
name='freedata-tnc',
|
name='freedata-tnc',
|
||||||
|
bundle_identifier='com.dj2ls.freedata-tnc',
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
strip=False,
|
strip=False,
|
||||||
|
|
|
@ -5,11 +5,12 @@ Created on Fri Dec 25 21:25:14 2020
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime,timezone
|
||||||
import crcengine
|
import crcengine
|
||||||
import static
|
import static
|
||||||
import structlog
|
import structlog
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import threading
|
||||||
|
|
||||||
log = structlog.get_logger("helpers")
|
log = structlog.get_logger("helpers")
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ def wait(seconds: float) -> bool:
|
||||||
timeout = time.time() + seconds
|
timeout = time.time() + seconds
|
||||||
|
|
||||||
while time.time() < timeout:
|
while time.time() < timeout:
|
||||||
time.sleep(0.01)
|
threading.Event().wait(0.01)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
|
||||||
# check if buffer empty
|
# check if buffer empty
|
||||||
if len(static.HEARD_STATIONS) == 0:
|
if len(static.HEARD_STATIONS) == 0:
|
||||||
static.HEARD_STATIONS.append(
|
static.HEARD_STATIONS.append(
|
||||||
[dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency]
|
[dxcallsign, dxgrid, int(datetime.now(timezone.utc).timestamp()), datatype, snr, offset, frequency]
|
||||||
)
|
)
|
||||||
# if not, we search and update
|
# if not, we search and update
|
||||||
else:
|
else:
|
||||||
|
@ -315,7 +316,7 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
|
||||||
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid)
|
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid)
|
||||||
return [True, bytes(call_with_ssid)]
|
return [True, bytes(call_with_ssid)]
|
||||||
|
|
||||||
return [False, ""]
|
return [False, b'']
|
||||||
|
|
||||||
|
|
||||||
def check_session_id(id: bytes, id_to_check: bytes):
|
def check_session_id(id: bytes, id_to_check: bytes):
|
||||||
|
@ -330,6 +331,8 @@ def check_session_id(id: bytes, id_to_check: bytes):
|
||||||
True
|
True
|
||||||
False
|
False
|
||||||
"""
|
"""
|
||||||
|
if id_to_check == b'\x00':
|
||||||
|
return False
|
||||||
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
||||||
return id == id_to_check
|
return id == id_to_check
|
||||||
|
|
||||||
|
@ -391,11 +394,7 @@ def decode_grid(b_code_word: bytes):
|
||||||
|
|
||||||
int_val = int(code_word & 0b111111111)
|
int_val = int(code_word & 0b111111111)
|
||||||
int_first, int_sec = divmod(int_val, 18)
|
int_first, int_sec = divmod(int_val, 18)
|
||||||
# int_first = int_val // 18
|
return chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||||
# int_sec = int_val % 18
|
|
||||||
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
|
|
||||||
def encode_call(call):
|
def encode_call(call):
|
||||||
|
|
|
@ -14,7 +14,7 @@ def setup_logging(filename: str = "", level: str = "DEBUG"):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
|
timestamper = structlog.processors.TimeStamper(fmt="iso")
|
||||||
pre_chain = [
|
pre_chain = [
|
||||||
# Add the log level and a timestamp to the event_dict if the log entry
|
# Add the log level and a timestamp to the event_dict if the log entry
|
||||||
# is not from structlog.
|
# is not from structlog.
|
||||||
|
|
142
tnc/main.py
142
tnc/main.py
|
@ -31,6 +31,7 @@ import modem
|
||||||
import static
|
import static
|
||||||
import structlog
|
import structlog
|
||||||
import explorer
|
import explorer
|
||||||
|
import json
|
||||||
|
|
||||||
log = structlog.get_logger("main")
|
log = structlog.get_logger("main")
|
||||||
|
|
||||||
|
@ -57,12 +58,28 @@ if __name__ == "__main__":
|
||||||
# --------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
PARSER = argparse.ArgumentParser(description="FreeDATA TNC")
|
PARSER = argparse.ArgumentParser(description="FreeDATA TNC")
|
||||||
|
|
||||||
|
#PARSER.add_argument(
|
||||||
|
# "--use-config",
|
||||||
|
# dest="configfile",
|
||||||
|
# action="store_true",
|
||||||
|
# help="Use the default config file config.ini",
|
||||||
|
#)
|
||||||
PARSER.add_argument(
|
PARSER.add_argument(
|
||||||
"--use-config",
|
"--use-config",
|
||||||
dest="configfile",
|
dest="configfile",
|
||||||
action="store_true",
|
default=False,
|
||||||
|
type=str,
|
||||||
help="Use the default config file config.ini",
|
help="Use the default config file config.ini",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PARSER.add_argument(
|
||||||
|
"--save-to-folder",
|
||||||
|
dest="savetofolder",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Save received data to local folder",
|
||||||
|
)
|
||||||
|
|
||||||
PARSER.add_argument(
|
PARSER.add_argument(
|
||||||
"--mycall",
|
"--mycall",
|
||||||
dest="mycall",
|
dest="mycall",
|
||||||
|
@ -90,14 +107,14 @@ if __name__ == "__main__":
|
||||||
dest="audio_input_device",
|
dest="audio_input_device",
|
||||||
default=0,
|
default=0,
|
||||||
help="listening sound card",
|
help="listening sound card",
|
||||||
type=int,
|
type=str,
|
||||||
)
|
)
|
||||||
PARSER.add_argument(
|
PARSER.add_argument(
|
||||||
"--tx",
|
"--tx",
|
||||||
dest="audio_output_device",
|
dest="audio_output_device",
|
||||||
default=0,
|
default=0,
|
||||||
help="transmitting sound card",
|
help="transmitting sound card",
|
||||||
type=int,
|
type=str,
|
||||||
)
|
)
|
||||||
PARSER.add_argument(
|
PARSER.add_argument(
|
||||||
"--port",
|
"--port",
|
||||||
|
@ -261,48 +278,13 @@ if __name__ == "__main__":
|
||||||
help="Enable sending tnc data to https://explorer.freedata.app",
|
help="Enable sending tnc data to https://explorer.freedata.app",
|
||||||
)
|
)
|
||||||
ARGS = PARSER.parse_args()
|
ARGS = PARSER.parse_args()
|
||||||
if ARGS.configfile:
|
|
||||||
# init config
|
|
||||||
config = config.CONFIG().read_config()
|
|
||||||
|
|
||||||
# additional step for being sure our callsign is correctly
|
# set save to folder state for allowing downloading files to local file system
|
||||||
# in case we are not getting a station ssid
|
static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder
|
||||||
# then we are forcing a station ssid = 0
|
|
||||||
mycallsign = bytes(config['STATION']['mycall'], "utf-8")
|
|
||||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
|
||||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
|
||||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
|
||||||
|
|
||||||
static.SSID_LIST = config['STATION']['ssid']
|
if not ARGS.configfile:
|
||||||
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
|
|
||||||
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
|
|
||||||
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
|
|
||||||
static.PORT = int(config['NETWORK']['tncport'])
|
|
||||||
# TODO: disabled because we don't need these settings anymore.
|
|
||||||
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
|
|
||||||
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
|
|
||||||
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
|
|
||||||
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
|
|
||||||
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
|
|
||||||
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
|
|
||||||
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
|
|
||||||
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
|
|
||||||
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
|
|
||||||
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
|
|
||||||
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
|
|
||||||
static.ENABLE_SCATTER = config['TNC']['scatter']
|
|
||||||
static.ENABLE_FFT = config['TNC']['fft']
|
|
||||||
static.ENABLE_FSK = False
|
|
||||||
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband']
|
|
||||||
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
|
|
||||||
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
|
|
||||||
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
|
|
||||||
static.RESPOND_TO_CQ = config['TNC']['qrv']
|
|
||||||
static.RX_BUFFER_SIZE = config['TNC']['rxbuffersize']
|
|
||||||
static.ENABLE_EXPLORER = config['TNC']['explorer']
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
# additional step for being sure our callsign is correctly
|
# additional step for being sure our callsign is correctly
|
||||||
# in case we are not getting a station ssid
|
# in case we are not getting a station ssid
|
||||||
# then we are forcing a station ssid = 0
|
# then we are forcing a station ssid = 0
|
||||||
|
@ -311,11 +293,24 @@ if __name__ == "__main__":
|
||||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||||
|
|
||||||
static.SSID_LIST = ARGS.ssid_list
|
static.SSID_LIST = ARGS.ssid_list
|
||||||
|
# check if own ssid is always part of ssid list
|
||||||
|
own_ssid = int(static.MYCALLSIGN.split(b"-")[1])
|
||||||
|
if own_ssid not in static.SSID_LIST:
|
||||||
|
static.SSID_LIST.append(own_ssid)
|
||||||
|
|
||||||
static.MYGRID = bytes(ARGS.mygrid, "utf-8")
|
static.MYGRID = bytes(ARGS.mygrid, "utf-8")
|
||||||
|
|
||||||
|
# check if we have an int or str as device name
|
||||||
|
try:
|
||||||
|
static.AUDIO_INPUT_DEVICE = int(ARGS.audio_input_device)
|
||||||
|
except ValueError:
|
||||||
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
|
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
|
||||||
|
try:
|
||||||
|
static.AUDIO_OUTPUT_DEVICE = int(ARGS.audio_output_device)
|
||||||
|
except ValueError:
|
||||||
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
||||||
|
|
||||||
static.PORT = ARGS.socket_port
|
static.PORT = ARGS.socket_port
|
||||||
static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name
|
static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name
|
||||||
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
|
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
|
||||||
|
@ -341,6 +336,67 @@ if __name__ == "__main__":
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("[DMN] Error reading config file", exception=e)
|
log.error("[DMN] Error reading config file", exception=e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
configfile = ARGS.configfile
|
||||||
|
# init config
|
||||||
|
config = config.CONFIG(configfile).read_config()
|
||||||
|
try:
|
||||||
|
# additional step for being sure our callsign is correctly
|
||||||
|
# in case we are not getting a station ssid
|
||||||
|
# then we are forcing a station ssid = 0
|
||||||
|
mycallsign = bytes(config['STATION']['mycall'], "utf-8")
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||||
|
|
||||||
|
#json.loads = for converting str list to list
|
||||||
|
static.SSID_LIST = json.loads(config['STATION']['ssid_list'])
|
||||||
|
|
||||||
|
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
|
||||||
|
# check if we have an int or str as device name
|
||||||
|
try:
|
||||||
|
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
|
||||||
|
except ValueError:
|
||||||
|
static.AUDIO_INPUT_DEVICE = config['AUDIO']['rx']
|
||||||
|
try:
|
||||||
|
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
|
||||||
|
except ValueError:
|
||||||
|
static.AUDIO_OUTPUT_DEVICE = config['AUDIO']['tx']
|
||||||
|
|
||||||
|
static.PORT = int(config['NETWORK']['tncport'])
|
||||||
|
# TODO: disabled because we don't need these settings anymore.
|
||||||
|
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
|
||||||
|
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
|
||||||
|
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
|
||||||
|
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
|
||||||
|
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
|
||||||
|
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
|
||||||
|
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
|
||||||
|
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
|
||||||
|
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
|
||||||
|
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
|
||||||
|
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
|
||||||
|
static.ENABLE_SCATTER = config['TNC']['scatter'] in ["True", "true", True]
|
||||||
|
static.ENABLE_FFT = config['TNC']['fft'] in ["True", "true", True]
|
||||||
|
static.ENABLE_FSK = False
|
||||||
|
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband'] in ["True", "true", True]
|
||||||
|
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
|
||||||
|
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
|
||||||
|
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
|
||||||
|
static.RESPOND_TO_CQ = config['TNC']['qrv'] in ["True", "true", True]
|
||||||
|
static.RX_BUFFER_SIZE = int(config['TNC']['rxbuffersize'])
|
||||||
|
static.ENABLE_EXPLORER = config['TNC']['explorer'] in ["True", "true", True]
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
log.warning("[CFG] Error reading config file near", key=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("[CFG] Error", e=e)
|
||||||
|
|
||||||
|
# make sure the own ssid is always part of the ssid list
|
||||||
|
my_ssid = int(static.MYCALLSIGN.split(b'-')[1])
|
||||||
|
if my_ssid not in static.SSID_LIST:
|
||||||
|
static.SSID_LIST.append(my_ssid)
|
||||||
|
|
||||||
# we need to wait until we got all parameters from argparse first before we can load the other modules
|
# we need to wait until we got all parameters from argparse first before we can load the other modules
|
||||||
import sock
|
import sock
|
||||||
|
|
||||||
|
@ -399,4 +455,4 @@ if __name__ == "__main__":
|
||||||
log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err)
|
log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
threading.Event().wait(1)
|
||||||
|
|
230
tnc/modem.py
230
tnc/modem.py
|
@ -5,6 +5,7 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
|
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
@ -15,15 +16,16 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import wave
|
||||||
import codec2
|
import codec2
|
||||||
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sock
|
import sock
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
import static
|
import static
|
||||||
import structlog
|
import structlog
|
||||||
import ujson as json
|
import ujson as json
|
||||||
from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE
|
from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE
|
||||||
|
|
||||||
TESTMODE = False
|
TESTMODE = False
|
||||||
RXCHANNEL = ""
|
RXCHANNEL = ""
|
||||||
|
@ -38,6 +40,13 @@ RECEIVE_DATAC1 = False
|
||||||
RECEIVE_DATAC3 = False
|
RECEIVE_DATAC3 = False
|
||||||
RECEIVE_FSK_LDPC_1 = False
|
RECEIVE_FSK_LDPC_1 = False
|
||||||
|
|
||||||
|
# state buffer
|
||||||
|
SIG0_DATAC0_STATE = []
|
||||||
|
SIG1_DATAC0_STATE = []
|
||||||
|
DAT0_DATAC1_STATE = []
|
||||||
|
DAT0_DATAC3_STATE = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RF:
|
class RF:
|
||||||
"""Class to encapsulate interactions between the audio device and codec2"""
|
"""Class to encapsulate interactions between the audio device and codec2"""
|
||||||
|
@ -60,6 +69,7 @@ class RF:
|
||||||
self.AUDIO_CHANNELS = 1
|
self.AUDIO_CHANNELS = 1
|
||||||
self.MODE = 0
|
self.MODE = 0
|
||||||
|
|
||||||
|
|
||||||
# Locking state for mod out so buffer will be filled before we can use it
|
# 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/127
|
||||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
# https://github.com/DJ2LS/FreeDATA/issues/99
|
||||||
|
@ -141,6 +151,10 @@ class RF:
|
||||||
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
|
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# INIT TX MODES
|
||||||
|
self.freedv_datac0_tx = open_codec2_instance(14)
|
||||||
|
self.freedv_datac1_tx = open_codec2_instance(10)
|
||||||
|
self.freedv_datac3_tx = open_codec2_instance(12)
|
||||||
# --------------------------------------------CREATE PYAUDIO INSTANCE
|
# --------------------------------------------CREATE PYAUDIO INSTANCE
|
||||||
if not TESTMODE:
|
if not TESTMODE:
|
||||||
try:
|
try:
|
||||||
|
@ -266,6 +280,13 @@ class RF:
|
||||||
)
|
)
|
||||||
hamlib_thread.start()
|
hamlib_thread.start()
|
||||||
|
|
||||||
|
hamlib_set_thread = threading.Thread(
|
||||||
|
target=self.set_rig_data, name="HAMLIB_SET_THREAD", daemon=True
|
||||||
|
)
|
||||||
|
hamlib_set_thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# self.log.debug("[MDM] Starting worker_receive")
|
# self.log.debug("[MDM] Starting worker_receive")
|
||||||
worker_received = threading.Thread(
|
worker_received = threading.Thread(
|
||||||
target=self.worker_received, name="WORKER_THREAD", daemon=True
|
target=self.worker_received, name="WORKER_THREAD", daemon=True
|
||||||
|
@ -284,7 +305,7 @@ class RF:
|
||||||
depositing the data into the codec data buffers.
|
depositing the data into the codec data buffers.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
time.sleep(0.01)
|
threading.Event().wait(0.01)
|
||||||
# -----read
|
# -----read
|
||||||
data_in48k = bytes()
|
data_in48k = bytes()
|
||||||
with open(RXCHANNEL, "rb") as fifo:
|
with open(RXCHANNEL, "rb") as fifo:
|
||||||
|
@ -307,7 +328,7 @@ class RF:
|
||||||
# (self.fsk_ldpc_buffer_1, static.ENABLE_FSK),
|
# (self.fsk_ldpc_buffer_1, static.ENABLE_FSK),
|
||||||
]:
|
]:
|
||||||
if (
|
if (
|
||||||
not data_buffer.nbuffer + length_x > data_buffer.size
|
not (data_buffer.nbuffer + length_x) > data_buffer.size
|
||||||
and receive
|
and receive
|
||||||
):
|
):
|
||||||
data_buffer.push(x)
|
data_buffer.push(x)
|
||||||
|
@ -315,14 +336,10 @@ class RF:
|
||||||
def mkfifo_write_callback(self) -> None:
|
def mkfifo_write_callback(self) -> None:
|
||||||
"""Support testing by writing the audio data to a pipe."""
|
"""Support testing by writing the audio data to a pipe."""
|
||||||
while True:
|
while True:
|
||||||
time.sleep(0.01)
|
threading.Event().wait(0.01)
|
||||||
|
|
||||||
# -----write
|
# -----write
|
||||||
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
if len(self.modoutqueue) > 0 and not self.mod_out_locked:
|
||||||
# data_out48k = np.zeros(self.AUDIO_FRAMES_PER_BUFFER_RX, dtype=np.int16)
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
data_out48k = self.modoutqueue.popleft()
|
data_out48k = self.modoutqueue.popleft()
|
||||||
# print(len(data_out48k))
|
# print(len(data_out48k))
|
||||||
|
|
||||||
|
@ -348,8 +365,14 @@ class RF:
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
x = self.resampler.resample48_to_8(x)
|
x = self.resampler.resample48_to_8(x)
|
||||||
|
|
||||||
|
# audio recording for debugging purposes
|
||||||
|
if static.AUDIO_RECORD:
|
||||||
|
#static.AUDIO_RECORD_FILE.write(x)
|
||||||
|
static.AUDIO_RECORD_FILE.writeframes(x)
|
||||||
|
|
||||||
# Avoid decoding when transmitting to reduce CPU
|
# Avoid decoding when transmitting to reduce CPU
|
||||||
if not static.TRANSMITTING:
|
# TODO: Overriding this for testing purposes
|
||||||
|
# if not static.TRANSMITTING:
|
||||||
length_x = len(x)
|
length_x = len(x)
|
||||||
|
|
||||||
# Avoid buffer overflow by filling only if buffer for
|
# Avoid buffer overflow by filling only if buffer for
|
||||||
|
@ -362,16 +385,24 @@ class RF:
|
||||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
|
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
|
||||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
|
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
|
||||||
]:
|
]:
|
||||||
if audiobuffer.nbuffer + length_x > audiobuffer.size:
|
if (audiobuffer.nbuffer + length_x) > audiobuffer.size:
|
||||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||||
elif receive:
|
elif receive:
|
||||||
audiobuffer.push(x)
|
audiobuffer.push(x)
|
||||||
|
# end of "not static.TRANSMITTING" if block
|
||||||
|
|
||||||
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
if not self.modoutqueue or self.mod_out_locked:
|
||||||
# if not self.modoutqueue or self.mod_out_locked:
|
|
||||||
data_out48k = np.zeros(frames, dtype=np.int16)
|
data_out48k = np.zeros(frames, dtype=np.int16)
|
||||||
self.fft_data = x
|
self.fft_data = x
|
||||||
else:
|
else:
|
||||||
|
if not static.PTT_STATE:
|
||||||
|
# TODO: Moved to this place for testing
|
||||||
|
# Maybe we can avoid moments of silence before transmitting
|
||||||
|
static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||||
|
jsondata = {"ptt": "True"}
|
||||||
|
data_out = json.dumps(jsondata)
|
||||||
|
sock.SOCKET_QUEUE.put(data_out)
|
||||||
|
|
||||||
data_out48k = self.modoutqueue.popleft()
|
data_out48k = self.modoutqueue.popleft()
|
||||||
self.fft_data = data_out48k
|
self.fft_data = data_out48k
|
||||||
|
|
||||||
|
@ -395,18 +426,37 @@ class RF:
|
||||||
frames:
|
frames:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.log.debug("[MDM] transmit", mode=mode)
|
|
||||||
|
"""
|
||||||
|
sig0 = 14
|
||||||
|
sig1 = 14
|
||||||
|
datac0 = 14
|
||||||
|
datac1 = 10
|
||||||
|
datac3 = 12
|
||||||
|
fsk_ldpc = 9
|
||||||
|
fsk_ldpc_0 = 200
|
||||||
|
fsk_ldpc_1 = 201
|
||||||
|
"""
|
||||||
|
if mode == 14:
|
||||||
|
freedv = self.freedv_datac0_tx
|
||||||
|
elif mode == 10:
|
||||||
|
freedv = self.freedv_datac1_tx
|
||||||
|
elif mode == 12:
|
||||||
|
freedv = self.freedv_datac3_tx
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
static.TRANSMITTING = True
|
static.TRANSMITTING = True
|
||||||
start_of_transmission = time.time()
|
start_of_transmission = time.time()
|
||||||
|
# TODO: Moved ptt toggle some steps before audio is ready for testing
|
||||||
# Toggle ptt early to save some time and send ptt state via socket
|
# Toggle ptt early to save some time and send ptt state via socket
|
||||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
# static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||||
jsondata = {"ptt": "True"}
|
# jsondata = {"ptt": "True"}
|
||||||
data_out = json.dumps(jsondata)
|
# data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(data_out)
|
# sock.SOCKET_QUEUE.put(data_out)
|
||||||
|
|
||||||
# Open codec2 instance
|
# Open codec2 instance
|
||||||
self.MODE = mode
|
self.MODE = mode
|
||||||
freedv = open_codec2_instance(self.MODE)
|
|
||||||
|
|
||||||
# Get number of bytes per frame for mode
|
# Get number of bytes per frame for mode
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
|
@ -431,11 +481,12 @@ class RF:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add empty data to handle ptt toggle time
|
# Add empty data to handle ptt toggle time
|
||||||
data_delay_mseconds = 0 # milliseconds
|
#data_delay_mseconds = 0 # milliseconds
|
||||||
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
#data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
#mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||||
txbuffer = bytes(mod_out_silence)
|
#txbuffer = bytes(mod_out_silence)
|
||||||
|
# TODO: Disabled this one for testing
|
||||||
|
txbuffer = bytes()
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
||||||
)
|
)
|
||||||
|
@ -499,8 +550,10 @@ class RF:
|
||||||
txbuffer_48k = self.resampler.resample8_to_48(x)
|
txbuffer_48k = self.resampler.resample8_to_48(x)
|
||||||
|
|
||||||
# Explicitly lock our usage of mod_out_queue if needed
|
# Explicitly lock our usage of mod_out_queue if needed
|
||||||
# Deactivated for testing purposes
|
# This could avoid audio problems on slower CPU
|
||||||
self.mod_out_locked = False
|
# we will fill our modout list with all data, then start
|
||||||
|
# processing it in audio callback
|
||||||
|
self.mod_out_locked = True
|
||||||
|
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800
|
chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800
|
||||||
|
@ -518,11 +571,11 @@ class RF:
|
||||||
|
|
||||||
self.modoutqueue.append(c)
|
self.modoutqueue.append(c)
|
||||||
|
|
||||||
# Release our mod_out_lock so we can use the queue
|
# Release our mod_out_lock, so we can use the queue
|
||||||
self.mod_out_locked = False
|
self.mod_out_locked = False
|
||||||
|
|
||||||
while self.modoutqueue:
|
while self.modoutqueue:
|
||||||
time.sleep(0.01)
|
threading.Event().wait(0.01)
|
||||||
|
|
||||||
static.PTT_STATE = self.hamlib.set_ptt(False)
|
static.PTT_STATE = self.hamlib.set_ptt(False)
|
||||||
|
|
||||||
|
@ -534,7 +587,6 @@ class RF:
|
||||||
# After processing, set the locking state back to true to be prepared for next transmission
|
# After processing, set the locking state back to true to be prepared for next transmission
|
||||||
self.mod_out_locked = True
|
self.mod_out_locked = True
|
||||||
|
|
||||||
self.c_lib.freedv_close(freedv)
|
|
||||||
self.modem_transmit_queue.task_done()
|
self.modem_transmit_queue.task_done()
|
||||||
static.TRANSMITTING = False
|
static.TRANSMITTING = False
|
||||||
threading.Event().set()
|
threading.Event().set()
|
||||||
|
@ -550,13 +602,15 @@ class RF:
|
||||||
freedv: ctypes.c_void_p,
|
freedv: ctypes.c_void_p,
|
||||||
bytes_out,
|
bytes_out,
|
||||||
bytes_per_frame,
|
bytes_per_frame,
|
||||||
|
state_buffer,
|
||||||
|
mode_name,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
De-modulate supplied audio stream with supplied codec2 instance.
|
De-modulate supplied audio stream with supplied codec2 instance.
|
||||||
Decoded audio is placed into `bytes_out`.
|
Decoded audio is placed into `bytes_out`.
|
||||||
|
|
||||||
:param buffer: Incoming audio
|
:param audiobuffer: Incoming audio
|
||||||
:type buffer: codec2.audio_buffer
|
:type audiobuffer: codec2.audio_buffer
|
||||||
:param nin: Number of frames codec2 is expecting
|
:param nin: Number of frames codec2 is expecting
|
||||||
:type nin: int
|
:type nin: int
|
||||||
:param freedv: codec2 instance
|
:param freedv: codec2 instance
|
||||||
|
@ -565,6 +619,10 @@ class RF:
|
||||||
:type bytes_out: _type_
|
:type bytes_out: _type_
|
||||||
:param bytes_per_frame: Number of bytes per frame
|
:param bytes_per_frame: Number of bytes per frame
|
||||||
:type bytes_per_frame: int
|
:type bytes_per_frame: int
|
||||||
|
:param state_buffer: modem states
|
||||||
|
:type state_buffer: int
|
||||||
|
:param mode_name: mode name
|
||||||
|
:type mode_name: str
|
||||||
:return: NIN from freedv instance
|
:return: NIN from freedv instance
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
@ -577,17 +635,39 @@ class RF:
|
||||||
nbytes = codec2.api.freedv_rawdatarx(
|
nbytes = codec2.api.freedv_rawdatarx(
|
||||||
freedv, bytes_out, audiobuffer.buffer.ctypes
|
freedv, bytes_out, audiobuffer.buffer.ctypes
|
||||||
)
|
)
|
||||||
|
# get current modem states and write to list
|
||||||
|
# 1 trial
|
||||||
|
# 2 sync
|
||||||
|
# 3 trial sync
|
||||||
|
# 6 decoded
|
||||||
|
# 10 error decoding == NACK
|
||||||
|
rx_status = codec2.api.freedv_get_rx_status(freedv)
|
||||||
|
|
||||||
|
if rx_status != 0:
|
||||||
|
# if we're receiving FreeDATA signals, reset channel busy state
|
||||||
|
static.CHANNEL_BUSY = False
|
||||||
|
|
||||||
|
self.log.debug(
|
||||||
|
"[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status, sync_flag=codec2.api.rx_sync_flags_to_text[rx_status]
|
||||||
|
)
|
||||||
|
|
||||||
|
if rx_status == 10:
|
||||||
|
state_buffer.append(rx_status)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
audiobuffer.pop(nin)
|
audiobuffer.pop(nin)
|
||||||
nin = codec2.api.freedv_nin(freedv)
|
nin = codec2.api.freedv_nin(freedv)
|
||||||
if nbytes == bytes_per_frame:
|
if nbytes == bytes_per_frame:
|
||||||
# process commands only if static.LISTEN = True
|
# process commands only if static.LISTEN = True
|
||||||
if static.LISTEN:
|
if static.LISTEN:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] [demod_audio] Pushing received data to received_queue"
|
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
|
||||||
)
|
)
|
||||||
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
|
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
|
||||||
self.get_scatter(freedv)
|
self.get_scatter(freedv)
|
||||||
self.calculate_snr(freedv)
|
self.calculate_snr(freedv)
|
||||||
|
state_buffer = []
|
||||||
else:
|
else:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"[MDM] [demod_audio] received frame but ignored processing",
|
"[MDM] [demod_audio] received frame but ignored processing",
|
||||||
|
@ -675,6 +755,8 @@ class RF:
|
||||||
self.sig0_datac0_freedv,
|
self.sig0_datac0_freedv,
|
||||||
self.sig0_datac0_bytes_out,
|
self.sig0_datac0_bytes_out,
|
||||||
self.sig0_datac0_bytes_per_frame,
|
self.sig0_datac0_bytes_per_frame,
|
||||||
|
SIG0_DATAC0_STATE,
|
||||||
|
"sig0-datac0"
|
||||||
)
|
)
|
||||||
|
|
||||||
def audio_sig1_datac0(self) -> None:
|
def audio_sig1_datac0(self) -> None:
|
||||||
|
@ -685,6 +767,8 @@ class RF:
|
||||||
self.sig1_datac0_freedv,
|
self.sig1_datac0_freedv,
|
||||||
self.sig1_datac0_bytes_out,
|
self.sig1_datac0_bytes_out,
|
||||||
self.sig1_datac0_bytes_per_frame,
|
self.sig1_datac0_bytes_per_frame,
|
||||||
|
SIG1_DATAC0_STATE,
|
||||||
|
"sig1-datac0"
|
||||||
)
|
)
|
||||||
|
|
||||||
def audio_dat0_datac1(self) -> None:
|
def audio_dat0_datac1(self) -> None:
|
||||||
|
@ -695,6 +779,8 @@ class RF:
|
||||||
self.dat0_datac1_freedv,
|
self.dat0_datac1_freedv,
|
||||||
self.dat0_datac1_bytes_out,
|
self.dat0_datac1_bytes_out,
|
||||||
self.dat0_datac1_bytes_per_frame,
|
self.dat0_datac1_bytes_per_frame,
|
||||||
|
DAT0_DATAC1_STATE,
|
||||||
|
"dat0-datac1"
|
||||||
)
|
)
|
||||||
|
|
||||||
def audio_dat0_datac3(self) -> None:
|
def audio_dat0_datac3(self) -> None:
|
||||||
|
@ -705,6 +791,8 @@ class RF:
|
||||||
self.dat0_datac3_freedv,
|
self.dat0_datac3_freedv,
|
||||||
self.dat0_datac3_bytes_out,
|
self.dat0_datac3_bytes_out,
|
||||||
self.dat0_datac3_bytes_per_frame,
|
self.dat0_datac3_bytes_per_frame,
|
||||||
|
DAT0_DATAC3_STATE,
|
||||||
|
"dat0-datac3"
|
||||||
)
|
)
|
||||||
|
|
||||||
def audio_fsk_ldpc_0(self) -> None:
|
def audio_fsk_ldpc_0(self) -> None:
|
||||||
|
@ -730,9 +818,14 @@ class RF:
|
||||||
def worker_transmit(self) -> None:
|
def worker_transmit(self) -> None:
|
||||||
"""Worker for FIFO queue for processing frames to be transmitted"""
|
"""Worker for FIFO queue for processing frames to be transmitted"""
|
||||||
while True:
|
while True:
|
||||||
|
# print queue size for debugging purposes
|
||||||
|
# TODO: Lets check why we have several frames in our transmit queue which causes sometimes a double transmission
|
||||||
|
# we could do a cleanup after a transmission so theres no reason sending twice
|
||||||
|
queuesize = self.modem_transmit_queue.qsize()
|
||||||
|
self.log.debug("[MDM] self.modem_transmit_queue", qsize=queuesize)
|
||||||
data = self.modem_transmit_queue.get()
|
data = self.modem_transmit_queue.get()
|
||||||
|
|
||||||
self.log.debug("[MDM] worker_transmit", mode=data[0])
|
# self.log.debug("[MDM] worker_transmit", mode=data[0])
|
||||||
self.transmit(
|
self.transmit(
|
||||||
mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3]
|
mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3]
|
||||||
)
|
)
|
||||||
|
@ -783,8 +876,16 @@ class RF:
|
||||||
)
|
)
|
||||||
|
|
||||||
scatterdata = []
|
scatterdata = []
|
||||||
for i in range(codec2.MODEM_STATS_NC_MAX):
|
# original function before itertool
|
||||||
for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
#for i in range(codec2.MODEM_STATS_NC_MAX):
|
||||||
|
# for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
||||||
|
# # print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||||
|
# xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||||
|
# ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||||
|
# if xsymbols != 0.0 and ysymbols != 0.0:
|
||||||
|
# scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
|
||||||
|
|
||||||
|
for i, j in itertools.product(range(codec2.MODEM_STATS_NC_MAX), range(1, codec2.MODEM_STATS_NR_MAX, 2)):
|
||||||
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||||
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||||
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||||
|
@ -798,7 +899,6 @@ class RF:
|
||||||
# only take every tenth data point
|
# only take every tenth data point
|
||||||
static.SCATTER = scatterdata[::10]
|
static.SCATTER = scatterdata[::10]
|
||||||
|
|
||||||
|
|
||||||
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
||||||
"""
|
"""
|
||||||
Ask codec2 for data about the received signal and calculate
|
Ask codec2 for data about the received signal and calculate
|
||||||
|
@ -832,6 +932,20 @@ class RF:
|
||||||
static.SNR = 0
|
static.SNR = 0
|
||||||
return static.SNR
|
return static.SNR
|
||||||
|
|
||||||
|
def set_rig_data(self) -> None:
|
||||||
|
"""
|
||||||
|
Set rigctld parameters like frequency, mode
|
||||||
|
THis needs to be processed in a queue
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
cmd = RIGCTLD_COMMAND_QUEUE.get()
|
||||||
|
if cmd[0] == "set_frequency":
|
||||||
|
# [1] = Frequency
|
||||||
|
self.hamlib.set_frequency(cmd[1])
|
||||||
|
if cmd[0] == "set_mode":
|
||||||
|
# [1] = Mode
|
||||||
|
self.hamlib.set_mode(cmd[1])
|
||||||
|
|
||||||
def update_rig_data(self) -> None:
|
def update_rig_data(self) -> None:
|
||||||
"""
|
"""
|
||||||
Request information about the current state of the radio via hamlib
|
Request information about the current state of the radio via hamlib
|
||||||
|
@ -841,7 +955,7 @@ class RF:
|
||||||
- static.HAMLIB_BANDWIDTH
|
- static.HAMLIB_BANDWIDTH
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
threading.Event().wait(0.5)
|
threading.Event().wait(0.25)
|
||||||
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
||||||
static.HAMLIB_MODE = self.hamlib.get_mode()
|
static.HAMLIB_MODE = self.hamlib.get_mode()
|
||||||
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
||||||
|
@ -859,7 +973,7 @@ class RF:
|
||||||
rms_counter = 0
|
rms_counter = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# time.sleep(0.01)
|
# threading.Event().wait(0.01)
|
||||||
threading.Event().wait(0.01)
|
threading.Event().wait(0.01)
|
||||||
# WE NEED TO OPTIMIZE THIS!
|
# WE NEED TO OPTIMIZE THIS!
|
||||||
|
|
||||||
|
@ -892,12 +1006,22 @@ class RF:
|
||||||
# calculate dbfs every 50 cycles for reducing CPU load
|
# calculate dbfs every 50 cycles for reducing CPU load
|
||||||
rms_counter += 1
|
rms_counter += 1
|
||||||
if rms_counter > 50:
|
if rms_counter > 50:
|
||||||
d = np.frombuffer(self.fft_data, np.int16).astype(np.float)
|
d = np.frombuffer(self.fft_data, np.int16).astype(np.float32)
|
||||||
# calculate RMS and then dBFS
|
# calculate RMS and then dBFS
|
||||||
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
||||||
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
|
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
|
||||||
|
# try except for avoiding runtime errors by division/0
|
||||||
|
try:
|
||||||
rms = int(np.sqrt(np.max(d ** 2)))
|
rms = int(np.sqrt(np.max(d ** 2)))
|
||||||
|
if rms == 0:
|
||||||
|
raise ZeroDivisionError
|
||||||
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning(
|
||||||
|
"[MDM] fft calculation error - please check your audio setup",
|
||||||
|
e=e,
|
||||||
|
)
|
||||||
|
static.AUDIO_DBFS = -100
|
||||||
|
|
||||||
rms_counter = 0
|
rms_counter = 0
|
||||||
|
|
||||||
|
@ -919,20 +1043,16 @@ class RF:
|
||||||
# 3200Hz = 315
|
# 3200Hz = 315
|
||||||
|
|
||||||
# define the area, we are detecting busy state
|
# define the area, we are detecting busy state
|
||||||
if static.LOW_BANDWIDTH_MODE:
|
dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231]
|
||||||
dfft = dfft[120:176]
|
|
||||||
else:
|
|
||||||
dfft = dfft[65:231]
|
|
||||||
|
|
||||||
|
|
||||||
# Check for signals higher than average by checking for "100"
|
# Check for signals higher than average by checking for "100"
|
||||||
# If we have a signal, increment our channel_busy delay counter
|
# If we have a signal, increment our channel_busy delay counter
|
||||||
# so we have a smoother state toggle
|
# so we have a smoother state toggle
|
||||||
if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING:
|
if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING:
|
||||||
static.CHANNEL_BUSY = True
|
static.CHANNEL_BUSY = True
|
||||||
# Limit delay counter to a maximum of 250. The higher this value,
|
# Limit delay counter to a maximum of 200. The higher this value,
|
||||||
# the longer we will wait until releasing state
|
# the longer we will wait until releasing state
|
||||||
channel_busy_delay = min(channel_busy_delay + 10, 250)
|
channel_busy_delay = min(channel_busy_delay + 10, 200)
|
||||||
else:
|
else:
|
||||||
# Decrement channel busy counter if no signal has been detected.
|
# Decrement channel busy counter if no signal has been detected.
|
||||||
channel_busy_delay = max(channel_busy_delay - 1, 0)
|
channel_busy_delay = max(channel_busy_delay - 1, 0)
|
||||||
|
@ -1032,3 +1152,19 @@ def set_audio_volume(datalist, volume: float) -> np.int16:
|
||||||
# Scale samples by the ratio of volume / 100.0
|
# Scale samples by the ratio of volume / 100.0
|
||||||
data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore
|
data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore
|
||||||
return data.astype(np.int16)
|
return data.astype(np.int16)
|
||||||
|
|
||||||
|
|
||||||
|
def get_modem_error_state():
|
||||||
|
"""
|
||||||
|
get current state buffer and return True of contains 10
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if RECEIVE_DATAC1 and 10 in DAT0_DATAC1_STATE:
|
||||||
|
DAT0_DATAC1_STATE.clear()
|
||||||
|
return True
|
||||||
|
if RECEIVE_DATAC3 and 10 in DAT0_DATAC3_STATE:
|
||||||
|
DAT0_DATAC3_STATE.clear()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
@ -13,3 +13,6 @@ MODEM_TRANSMIT_QUEUE = queue.Queue()
|
||||||
|
|
||||||
# Initialize FIFO queue to finally store received data
|
# Initialize FIFO queue to finally store received data
|
||||||
RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE)
|
RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE)
|
||||||
|
|
||||||
|
# Commands we want to send to rigctld
|
||||||
|
RIGCTLD_COMMAND_QUEUE = queue.Queue()
|
170
tnc/rigctld.py
170
tnc/rigctld.py
|
@ -4,9 +4,11 @@
|
||||||
#
|
#
|
||||||
# modified and adjusted to FreeDATA needs by DJ2LS
|
# modified and adjusted to FreeDATA needs by DJ2LS
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import structlog
|
import structlog
|
||||||
|
import threading
|
||||||
|
|
||||||
# set global hamlib version
|
# set global hamlib version
|
||||||
hamlib_version = 0
|
hamlib_version = 0
|
||||||
|
@ -19,9 +21,11 @@ class radio:
|
||||||
|
|
||||||
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
|
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
|
||||||
"""Open a connection to rigctld, and test it for validity"""
|
"""Open a connection to rigctld, and test it for validity"""
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.ptt_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
self.connected = False
|
self.ptt_connected = False
|
||||||
|
self.data_connected = False
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.port = port
|
self.port = port
|
||||||
self.connection_attempts = 5
|
self.connection_attempts = 5
|
||||||
|
@ -31,7 +35,6 @@ class radio:
|
||||||
self.frequency = ''
|
self.frequency = ''
|
||||||
self.mode = ''
|
self.mode = ''
|
||||||
|
|
||||||
|
|
||||||
def open_rig(
|
def open_rig(
|
||||||
self,
|
self,
|
||||||
devicename,
|
devicename,
|
||||||
|
@ -65,8 +68,20 @@ class radio:
|
||||||
self.hostname = rigctld_ip
|
self.hostname = rigctld_ip
|
||||||
self.port = int(rigctld_port)
|
self.port = int(rigctld_port)
|
||||||
|
|
||||||
if self.connect():
|
#_ptt_connect = self.ptt_connect()
|
||||||
self.log.debug("Rigctl initialized")
|
#_data_connect = self.data_connect()
|
||||||
|
|
||||||
|
ptt_thread = threading.Thread(target=self.ptt_connect, args=[], daemon=True)
|
||||||
|
ptt_thread.start()
|
||||||
|
|
||||||
|
data_thread = threading.Thread(target=self.data_connect, args=[], daemon=True)
|
||||||
|
data_thread.start()
|
||||||
|
|
||||||
|
# wait some time
|
||||||
|
threading.Event().wait(0.5)
|
||||||
|
|
||||||
|
if self.ptt_connected and self.data_connected:
|
||||||
|
self.log.debug("Rigctl DATA/PTT initialized")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self.log.error(
|
self.log.error(
|
||||||
|
@ -74,33 +89,58 @@ class radio:
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def connect(self):
|
def ptt_connect(self):
|
||||||
"""Connect to rigctld instance"""
|
"""Connect to rigctld instance"""
|
||||||
if not self.connected:
|
while True:
|
||||||
|
|
||||||
|
if not self.ptt_connected:
|
||||||
try:
|
try:
|
||||||
self.connection = socket.create_connection((self.hostname, self.port))
|
self.ptt_connection = socket.create_connection((self.hostname, self.port))
|
||||||
self.connected = True
|
self.ptt_connected = True
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port
|
"[RIGCTLD] Connected PTT instance to rigctld!", ip=self.hostname, port=self.port
|
||||||
)
|
)
|
||||||
return True
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
# ConnectionRefusedError: [Errno 111] Connection refused
|
# ConnectionRefusedError: [Errno 111] Connection refused
|
||||||
self.close_rig()
|
self.close_rig()
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"[RIGCTLD] Reconnect...",
|
"[RIGCTLD] PTT Reconnect...",
|
||||||
ip=self.hostname,
|
ip=self.hostname,
|
||||||
port=self.port,
|
port=self.port,
|
||||||
e=err,
|
e=err,
|
||||||
)
|
)
|
||||||
return False
|
|
||||||
|
threading.Event().wait(0.5)
|
||||||
|
|
||||||
|
def data_connect(self):
|
||||||
|
"""Connect to rigctld instance"""
|
||||||
|
while True:
|
||||||
|
if not self.data_connected:
|
||||||
|
try:
|
||||||
|
self.data_connection = socket.create_connection((self.hostname, self.port))
|
||||||
|
self.data_connected = True
|
||||||
|
self.log.info(
|
||||||
|
"[RIGCTLD] Connected DATA instance to rigctld!", ip=self.hostname, port=self.port
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
# ConnectionRefusedError: [Errno 111] Connection refused
|
||||||
|
self.close_rig()
|
||||||
|
self.log.warning(
|
||||||
|
"[RIGCTLD] DATA Reconnect...",
|
||||||
|
ip=self.hostname,
|
||||||
|
port=self.port,
|
||||||
|
e=err,
|
||||||
|
)
|
||||||
|
threading.Event().wait(0.5)
|
||||||
|
|
||||||
def close_rig(self):
|
def close_rig(self):
|
||||||
""" """
|
""" """
|
||||||
self.sock.close()
|
self.ptt_sock.close()
|
||||||
self.connected = False
|
self.data_sock.close()
|
||||||
|
self.ptt_connected = False
|
||||||
|
self.data_connected = False
|
||||||
|
|
||||||
def send_command(self, command) -> bytes:
|
def send_ptt_command(self, command, expect_answer) -> bytes:
|
||||||
"""Send a command to the connected rotctld instance,
|
"""Send a command to the connected rotctld instance,
|
||||||
and return the return value.
|
and return the return value.
|
||||||
|
|
||||||
|
@ -108,9 +148,9 @@ class radio:
|
||||||
command:
|
command:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.connected:
|
if self.ptt_connected:
|
||||||
try:
|
try:
|
||||||
self.connection.sendall(command + b"\n")
|
self.ptt_connection.sendall(command + b"\n")
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"[RIGCTLD] Command not executed!",
|
"[RIGCTLD] Command not executed!",
|
||||||
|
@ -118,10 +158,33 @@ class radio:
|
||||||
ip=self.hostname,
|
ip=self.hostname,
|
||||||
port=self.port,
|
port=self.port,
|
||||||
)
|
)
|
||||||
self.connected = False
|
self.ptt_connected = False
|
||||||
|
return b""
|
||||||
|
|
||||||
|
def send_data_command(self, command, expect_answer) -> bytes:
|
||||||
|
"""Send a command to the connected rotctld instance,
|
||||||
|
and return the return value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.data_connected:
|
||||||
|
try:
|
||||||
|
self.data_connection.sendall(command + b"\n")
|
||||||
|
except Exception:
|
||||||
|
self.log.warning(
|
||||||
|
"[RIGCTLD] Command not executed!",
|
||||||
|
command=command,
|
||||||
|
ip=self.hostname,
|
||||||
|
port=self.port,
|
||||||
|
)
|
||||||
|
self.data_connected = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.connection.recv(1024)
|
# recv seems to be blocking so in case of ptt we don't need the response
|
||||||
|
# maybe this speeds things up and avoids blocking states
|
||||||
|
return self.data_connection.recv(64) if expect_answer else True
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"[RIGCTLD] No command response!",
|
"[RIGCTLD] No command response!",
|
||||||
|
@ -129,27 +192,24 @@ class radio:
|
||||||
ip=self.hostname,
|
ip=self.hostname,
|
||||||
port=self.port,
|
port=self.port,
|
||||||
)
|
)
|
||||||
self.connected = False
|
self.data_connected = False
|
||||||
else:
|
|
||||||
|
|
||||||
# reconnecting....
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
""" """
|
""" """
|
||||||
return "connected" if self.connected else "unknown/disconnected"
|
return "connected" if self.data_connected and self.ptt_connected else "unknown/disconnected"
|
||||||
|
|
||||||
def get_mode(self):
|
def get_mode(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"m")
|
data = self.send_data_command(b"m", True)
|
||||||
data = data.split(b"\n")
|
data = data.split(b"\n")
|
||||||
data = data[0].decode("utf-8")
|
data = data[0].decode("utf-8")
|
||||||
if 'RPRT' not in data:
|
if 'RPRT' not in data:
|
||||||
self.mode = data
|
try:
|
||||||
|
data = int(data)
|
||||||
|
except ValueError:
|
||||||
|
self.mode = str(data)
|
||||||
|
|
||||||
return self.mode
|
return self.mode
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -158,11 +218,12 @@ class radio:
|
||||||
def get_bandwidth(self):
|
def get_bandwidth(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"m")
|
data = self.send_data_command(b"m", True)
|
||||||
data = data.split(b"\n")
|
data = data.split(b"\n")
|
||||||
data = data[1].decode("utf-8")
|
data = data[1].decode("utf-8")
|
||||||
|
|
||||||
if 'RPRT' not in data:
|
if 'RPRT' not in data and data not in ['']:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
self.bandwidth = int(data)
|
self.bandwidth = int(data)
|
||||||
return self.bandwidth
|
return self.bandwidth
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -171,11 +232,14 @@ class radio:
|
||||||
def get_frequency(self):
|
def get_frequency(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"f")
|
data = self.send_data_command(b"f", True)
|
||||||
data = data.decode("utf-8")
|
data = data.decode("utf-8")
|
||||||
if 'RPRT' not in data:
|
if 'RPRT' not in data and data not in [0, '0', '']:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
data = int(data)
|
||||||
|
# make sure we have a frequency and not bandwidth
|
||||||
|
if data >= 10000:
|
||||||
self.frequency = data
|
self.frequency = data
|
||||||
|
|
||||||
return self.frequency
|
return self.frequency
|
||||||
except Exception:
|
except Exception:
|
||||||
return self.frequency
|
return self.frequency
|
||||||
|
@ -183,7 +247,7 @@ class radio:
|
||||||
def get_ptt(self):
|
def get_ptt(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
return self.send_command(b"t")
|
return self.send_command(b"t", True)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -198,9 +262,39 @@ class radio:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if state:
|
if state:
|
||||||
self.send_command(b"T 1")
|
self.send_ptt_command(b"T 1", False)
|
||||||
else:
|
else:
|
||||||
self.send_command(b"T 0")
|
self.send_ptt_command(b"T 0", False)
|
||||||
return state
|
return state
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def set_frequency(self, frequency):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frequency:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
command = bytes(f"F {frequency}", "utf-8")
|
||||||
|
self.send_data_command(command, False)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_mode(self, mode):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
command = bytes(f"M {mode} {self.bandwidth}", "utf-8")
|
||||||
|
self.send_data_command(command, False)
|
||||||
|
except Exception:
|
||||||
|
return False
|
|
@ -30,6 +30,9 @@ class radio:
|
||||||
""" """
|
""" """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def set_bandwidth(self):
|
||||||
|
""" """
|
||||||
|
return None
|
||||||
def set_mode(self, mode):
|
def set_mode(self, mode):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -41,6 +44,16 @@ class radio:
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def set_frequency(self, frequency):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
return None
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
125
tnc/sock.py
125
tnc/sock.py
|
@ -24,13 +24,14 @@ import socketserver
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import wave
|
||||||
|
|
||||||
import helpers
|
import helpers
|
||||||
import static
|
import static
|
||||||
import structlog
|
import structlog
|
||||||
import ujson as json
|
import ujson as json
|
||||||
from exceptions import NoCallsign
|
from exceptions import NoCallsign
|
||||||
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER
|
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE
|
||||||
|
|
||||||
SOCKET_QUEUE = queue.Queue()
|
SOCKET_QUEUE = queue.Queue()
|
||||||
DAEMON_QUEUE = queue.Queue()
|
DAEMON_QUEUE = queue.Queue()
|
||||||
|
@ -76,7 +77,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
if data != tempdata:
|
if data != tempdata:
|
||||||
tempdata = data
|
tempdata = data
|
||||||
SOCKET_QUEUE.put(data)
|
SOCKET_QUEUE.put(data)
|
||||||
time.sleep(0.5)
|
threading.Event().wait(0.5)
|
||||||
|
|
||||||
while not SOCKET_QUEUE.empty():
|
while not SOCKET_QUEUE.empty():
|
||||||
data = SOCKET_QUEUE.get()
|
data = SOCKET_QUEUE.get()
|
||||||
|
@ -84,20 +85,23 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
sock_data += b"\n" # append line limiter
|
sock_data += b"\n" # append line limiter
|
||||||
|
|
||||||
# send data to all clients
|
# send data to all clients
|
||||||
# try:
|
try:
|
||||||
for client in CONNECTED_CLIENTS:
|
for client in CONNECTED_CLIENTS:
|
||||||
try:
|
try:
|
||||||
client.send(sock_data)
|
client.send(sock_data)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.log.info("[SCK] Connection lost", e=err)
|
self.log.info("[SCK] Connection lost", e=err)
|
||||||
|
# TODO: Check if we really should set connection alive to false. This might disconnect all other clients as well...
|
||||||
self.connection_alive = False
|
self.connection_alive = False
|
||||||
|
except Exception as err:
|
||||||
|
self.log.debug("[SCK] catch harmless RuntimeError: Set changed size during iteration", e=err)
|
||||||
|
|
||||||
# we want to transmit scatter data only once to reduce network traffic
|
# we want to transmit scatter data only once to reduce network traffic
|
||||||
static.SCATTER = []
|
static.SCATTER = []
|
||||||
# we want to display INFO messages only once
|
# we want to display INFO messages only once
|
||||||
static.INFO = []
|
static.INFO = []
|
||||||
# self.request.sendall(sock_data)
|
# self.request.sendall(sock_data)
|
||||||
time.sleep(0.15)
|
threading.Event().wait(0.15)
|
||||||
|
|
||||||
def receive_from_client(self):
|
def receive_from_client(self):
|
||||||
"""
|
"""
|
||||||
|
@ -132,7 +136,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
# we might improve this by only processing one command or
|
# we might improve this by only processing one command or
|
||||||
# doing some kind of selection to determin which commands need to be dropped
|
# doing some kind of selection to determin which commands need to be dropped
|
||||||
# and which one can be processed during a running transmission
|
# and which one can be processed during a running transmission
|
||||||
time.sleep(3)
|
threading.Event().wait(0.5)
|
||||||
|
|
||||||
# finally delete our rx buffer to be ready for new commands
|
# finally delete our rx buffer to be ready for new commands
|
||||||
data = bytes()
|
data = bytes()
|
||||||
|
@ -169,7 +173,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
|
|
||||||
# keep connection alive until we close it
|
# keep connection alive until we close it
|
||||||
while self.connection_alive and not CLOSE_SIGNAL:
|
while self.connection_alive and not CLOSE_SIGNAL:
|
||||||
time.sleep(1)
|
threading.Event().wait(1)
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
""" """
|
""" """
|
||||||
|
@ -224,6 +228,40 @@ def process_tnc_commands(data):
|
||||||
"[SCK] CQ command execution error", e=err, command=received_json
|
"[SCK] CQ command execution error", e=err, command=received_json
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# START STOP AUDIO RECORDING -----------------------------------------------------
|
||||||
|
if received_json["type"] == "set" and received_json["command"] == "record_audio":
|
||||||
|
try:
|
||||||
|
if not static.AUDIO_RECORD:
|
||||||
|
static.AUDIO_RECORD_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w')
|
||||||
|
static.AUDIO_RECORD_FILE.setnchannels(1)
|
||||||
|
static.AUDIO_RECORD_FILE.setsampwidth(2)
|
||||||
|
static.AUDIO_RECORD_FILE.setframerate(8000)
|
||||||
|
static.AUDIO_RECORD = True
|
||||||
|
else:
|
||||||
|
static.AUDIO_RECORD = False
|
||||||
|
static.AUDIO_RECORD_FILE.close()
|
||||||
|
|
||||||
|
command_response("respond_to_call", True)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
command_response("respond_to_call", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] CQ command execution error", e=err, command=received_json
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# SET ENABLE/DISABLE RESPOND TO CALL -----------------------------------------------------
|
||||||
|
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
|
||||||
|
try:
|
||||||
|
static.RESPOND_TO_CALL = received_json["state"] in ['true', 'True', True]
|
||||||
|
command_response("respond_to_call", True)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
command_response("respond_to_call", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] CQ command execution error", e=err, command=received_json
|
||||||
|
)
|
||||||
|
|
||||||
# SET ENABLE RESPOND TO CQ -----------------------------------------------------
|
# SET ENABLE RESPOND TO CQ -----------------------------------------------------
|
||||||
if received_json["type"] == "set" and received_json["command"] == "respond_to_cq":
|
if received_json["type"] == "set" and received_json["command"] == "respond_to_cq":
|
||||||
try:
|
try:
|
||||||
|
@ -319,13 +357,22 @@ def process_tnc_commands(data):
|
||||||
if not str(dxcallsign).strip():
|
if not str(dxcallsign).strip():
|
||||||
raise NoCallsign
|
raise NoCallsign
|
||||||
|
|
||||||
# additional step for beeing sure our callsign is correctly
|
# additional step for being sure our callsign is correctly
|
||||||
# in case we are not getting a station ssid
|
# in case we are not getting a station ssid
|
||||||
# then we are forcing a station ssid = 0
|
# then we are forcing a station ssid = 0
|
||||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||||
|
|
||||||
DATA_QUEUE_TRANSMIT.put(["PING", dxcallsign])
|
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||||
|
try:
|
||||||
|
mycallsign = received_json["mycallsign"]
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
mycallsign = static.MYCALLSIGN
|
||||||
|
|
||||||
|
DATA_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign])
|
||||||
command_response("ping", True)
|
command_response("ping", True)
|
||||||
except NoCallsign:
|
except NoCallsign:
|
||||||
command_response("ping", False)
|
command_response("ping", False)
|
||||||
|
@ -351,6 +398,15 @@ def process_tnc_commands(data):
|
||||||
|
|
||||||
dxcallsign = received_json["dxcallsign"]
|
dxcallsign = received_json["dxcallsign"]
|
||||||
|
|
||||||
|
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||||
|
try:
|
||||||
|
mycallsign = received_json["mycallsign"]
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
mycallsign = static.MYCALLSIGN
|
||||||
|
|
||||||
# additional step for being sure our callsign is correctly
|
# additional step for being sure our callsign is correctly
|
||||||
# in case we are not getting a station ssid
|
# in case we are not getting a station ssid
|
||||||
# then we are forcing a station ssid = 0
|
# then we are forcing a station ssid = 0
|
||||||
|
@ -370,10 +426,8 @@ def process_tnc_commands(data):
|
||||||
|
|
||||||
# try connecting
|
# try connecting
|
||||||
try:
|
try:
|
||||||
static.DXCALLSIGN = dxcallsign
|
|
||||||
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
|
||||||
|
|
||||||
DATA_QUEUE_TRANSMIT.put(["CONNECT", dxcallsign, attempts])
|
DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts])
|
||||||
command_response("connect", True)
|
command_response("connect", True)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
command_response("connect", False)
|
command_response("connect", False)
|
||||||
|
@ -385,7 +439,6 @@ def process_tnc_commands(data):
|
||||||
# allow beacon transmission again
|
# allow beacon transmission again
|
||||||
static.BEACON_PAUSE = False
|
static.BEACON_PAUSE = False
|
||||||
|
|
||||||
|
|
||||||
# allow beacon transmission again
|
# allow beacon transmission again
|
||||||
static.BEACON_PAUSE = False
|
static.BEACON_PAUSE = False
|
||||||
|
|
||||||
|
@ -439,6 +492,9 @@ def process_tnc_commands(data):
|
||||||
# check if specific callsign is set with different SSID than the TNC is initialized
|
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||||
try:
|
try:
|
||||||
mycallsign = received_json["parameter"][0]["mycallsign"]
|
mycallsign = received_json["parameter"][0]["mycallsign"]
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
mycallsign = static.MYCALLSIGN
|
mycallsign = static.MYCALLSIGN
|
||||||
|
|
||||||
|
@ -462,7 +518,7 @@ def process_tnc_commands(data):
|
||||||
binarydata = base64.b64decode(base64data)
|
binarydata = base64.b64decode(base64data)
|
||||||
|
|
||||||
DATA_QUEUE_TRANSMIT.put(
|
DATA_QUEUE_TRANSMIT.put(
|
||||||
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, attempts]
|
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, dxcallsign, attempts]
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
@ -538,6 +594,32 @@ def process_tnc_commands(data):
|
||||||
command=received_json,
|
command=received_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# SET FREQUENCY -----------------------------------------------------
|
||||||
|
if received_json["command"] == "frequency" and received_json["type"] == "set":
|
||||||
|
try:
|
||||||
|
RIGCTLD_COMMAND_QUEUE.put(["set_frequency", received_json["frequency"]])
|
||||||
|
command_response("set_frequency", True)
|
||||||
|
except Exception as err:
|
||||||
|
command_response("set_frequency", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] Set frequency command execution error",
|
||||||
|
e=err,
|
||||||
|
command=received_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
# SET MODE -----------------------------------------------------
|
||||||
|
if received_json["command"] == "mode" and received_json["type"] == "set":
|
||||||
|
try:
|
||||||
|
RIGCTLD_COMMAND_QUEUE.put(["set_mode", received_json["mode"]])
|
||||||
|
command_response("set_mode", True)
|
||||||
|
except Exception as err:
|
||||||
|
command_response("set_mode", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] Set mode command execution error",
|
||||||
|
e=err,
|
||||||
|
command=received_json,
|
||||||
|
)
|
||||||
|
|
||||||
# exception, if JSON cant be decoded
|
# exception, if JSON cant be decoded
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.error("[SCK] JSON decoding error", e=err)
|
log.error("[SCK] JSON decoding error", e=err)
|
||||||
|
@ -569,16 +651,20 @@ def send_tnc_state():
|
||||||
"rx_msg_buffer_length": str(len(static.RX_MSG_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": str(static.ARQ_BYTES_PER_MINUTE),
|
||||||
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
|
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
|
||||||
|
"arq_seconds_until_finish": str(static.ARQ_SECONDS_UNTIL_FINISH),
|
||||||
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
|
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
|
||||||
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
|
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
|
||||||
|
"speed_list": static.SPEED_LIST,
|
||||||
"total_bytes": str(static.TOTAL_BYTES),
|
"total_bytes": str(static.TOTAL_BYTES),
|
||||||
"beacon_state": str(static.BEACON_STATE),
|
"beacon_state": str(static.BEACON_STATE),
|
||||||
"stations": [],
|
"stations": [],
|
||||||
"mycallsign": str(static.MYCALLSIGN, encoding),
|
"mycallsign": str(static.MYCALLSIGN, encoding),
|
||||||
|
"mygrid": str(static.MYGRID, encoding),
|
||||||
"dxcallsign": str(static.DXCALLSIGN, encoding),
|
"dxcallsign": str(static.DXCALLSIGN, encoding),
|
||||||
"dxgrid": str(static.DXGRID, encoding),
|
"dxgrid": str(static.DXGRID, encoding),
|
||||||
"hamlib_status": static.HAMLIB_STATUS,
|
"hamlib_status": static.HAMLIB_STATUS,
|
||||||
"listen": str(static.LISTEN),
|
"listen": str(static.LISTEN),
|
||||||
|
"audio_recording": str(static.AUDIO_RECORD),
|
||||||
}
|
}
|
||||||
|
|
||||||
# add heard stations to heard stations object
|
# add heard stations to heard stations object
|
||||||
|
@ -594,7 +680,6 @@ def send_tnc_state():
|
||||||
"frequency": heard[6],
|
"frequency": heard[6],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return json.dumps(output)
|
return json.dumps(output)
|
||||||
|
|
||||||
|
|
||||||
|
@ -690,6 +775,16 @@ def process_daemon_commands(data):
|
||||||
rx_buffer_size = str(received_json["parameter"][0]["rx_buffer_size"])
|
rx_buffer_size = str(received_json["parameter"][0]["rx_buffer_size"])
|
||||||
enable_explorer = str(received_json["parameter"][0]["enable_explorer"])
|
enable_explorer = str(received_json["parameter"][0]["enable_explorer"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
# convert ssid list to python list
|
||||||
|
ssid_list = str(received_json["parameter"][0]["ssid_list"])
|
||||||
|
ssid_list = ssid_list.replace(" ", "")
|
||||||
|
ssid_list = ssid_list.split(",")
|
||||||
|
# convert str to int
|
||||||
|
ssid_list = list(map(int, ssid_list))
|
||||||
|
except KeyError:
|
||||||
|
ssid_list = [0]
|
||||||
|
|
||||||
# print some debugging parameters
|
# print some debugging parameters
|
||||||
for item in received_json["parameter"][0]:
|
for item in received_json["parameter"][0]:
|
||||||
log.debug(
|
log.debug(
|
||||||
|
@ -725,6 +820,7 @@ def process_daemon_commands(data):
|
||||||
respond_to_cq,
|
respond_to_cq,
|
||||||
rx_buffer_size,
|
rx_buffer_size,
|
||||||
enable_explorer,
|
enable_explorer,
|
||||||
|
ssid_list,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
command_response("start_tnc", True)
|
command_response("start_tnc", True)
|
||||||
|
@ -820,3 +916,4 @@ def command_response(command, status):
|
||||||
jsondata = {"command_response": command, "status": s_status}
|
jsondata = {"command_response": command, "status": s_status}
|
||||||
data_out = json.dumps(jsondata)
|
data_out = json.dumps(jsondata)
|
||||||
SOCKET_QUEUE.put(data_out)
|
SOCKET_QUEUE.put(data_out)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ Not nice, suggestions are appreciated :-)
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
VERSION = "0.6.7-alpha.1"
|
VERSION = "0.6.12-alpha.6"
|
||||||
|
|
||||||
ENABLE_EXPLORER = False
|
ENABLE_EXPLORER = False
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ TNCPROCESS: subprocess.Popen
|
||||||
MYCALLSIGN: bytes = b"AA0AA"
|
MYCALLSIGN: bytes = b"AA0AA"
|
||||||
MYCALLSIGN_CRC: bytes = b"A"
|
MYCALLSIGN_CRC: bytes = b"A"
|
||||||
|
|
||||||
DXCALLSIGN: bytes = b"AA0AA"
|
DXCALLSIGN: bytes = b"ZZ9YY"
|
||||||
DXCALLSIGN_CRC: bytes = b"A"
|
DXCALLSIGN_CRC: bytes = b"A"
|
||||||
|
|
||||||
MYGRID: bytes = b""
|
MYGRID: bytes = b""
|
||||||
|
@ -73,6 +73,7 @@ SCATTER: list = []
|
||||||
ENABLE_SCATTER: bool = False
|
ENABLE_SCATTER: bool = False
|
||||||
ENABLE_FSK: bool = False
|
ENABLE_FSK: bool = False
|
||||||
RESPOND_TO_CQ: bool = False
|
RESPOND_TO_CQ: bool = False
|
||||||
|
RESPOND_TO_CALL: bool = True # respond to cq, ping, connection request, file request if not in session
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
|
|
||||||
# Audio Defaults
|
# Audio Defaults
|
||||||
|
@ -81,25 +82,31 @@ AUDIO_INPUT_DEVICES: list = []
|
||||||
AUDIO_OUTPUT_DEVICES: list = []
|
AUDIO_OUTPUT_DEVICES: list = []
|
||||||
AUDIO_INPUT_DEVICE: int = -2
|
AUDIO_INPUT_DEVICE: int = -2
|
||||||
AUDIO_OUTPUT_DEVICE: int = -2
|
AUDIO_OUTPUT_DEVICE: int = -2
|
||||||
|
AUDIO_RECORD: bool = False
|
||||||
|
AUDIO_RECORD_FILE = ''
|
||||||
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
||||||
|
|
||||||
AUDIO_DBFS: int = 0
|
AUDIO_DBFS: int = 0
|
||||||
FFT: list = [0]
|
FFT: list = [0]
|
||||||
ENABLE_FFT: bool = False
|
ENABLE_FFT: bool = True
|
||||||
CHANNEL_BUSY: bool = False
|
CHANNEL_BUSY: bool = False
|
||||||
|
|
||||||
# ARQ PROTOCOL VERSION
|
# ARQ PROTOCOL VERSION
|
||||||
ARQ_PROTOCOL_VERSION: int = 3
|
ARQ_PROTOCOL_VERSION: int = 5
|
||||||
|
|
||||||
# ARQ statistics
|
# ARQ statistics
|
||||||
|
SPEED_LIST: list = []
|
||||||
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
||||||
ARQ_BYTES_PER_MINUTE: int = 0
|
ARQ_BYTES_PER_MINUTE: int = 0
|
||||||
ARQ_BITS_PER_SECOND_BURST: int = 0
|
ARQ_BITS_PER_SECOND_BURST: int = 0
|
||||||
ARQ_BITS_PER_SECOND: int = 0
|
ARQ_BITS_PER_SECOND: int = 0
|
||||||
ARQ_COMPRESSION_FACTOR: int = 0
|
ARQ_COMPRESSION_FACTOR: int = 0
|
||||||
ARQ_TRANSMISSION_PERCENT: int = 0
|
ARQ_TRANSMISSION_PERCENT: int = 0
|
||||||
|
ARQ_SECONDS_UNTIL_FINISH: int = 0
|
||||||
ARQ_SPEED_LEVEL: int = 0
|
ARQ_SPEED_LEVEL: int = 0
|
||||||
TOTAL_BYTES: int = 0
|
TOTAL_BYTES: int = 0
|
||||||
|
# set save to folder state for allowing downloading files to local file system
|
||||||
|
ARQ_SAVE_TO_FOLDER: bool = False
|
||||||
|
|
||||||
# CHANNEL_STATE = 'RECEIVING_SIGNALLING'
|
# CHANNEL_STATE = 'RECEIVING_SIGNALLING'
|
||||||
TNC_STATE: str = "IDLE"
|
TNC_STATE: str = "IDLE"
|
||||||
|
|
104
tools/freedata_cli_tools.py
Executable file
104
tools/freedata_cli_tools.py
Executable file
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
@author: DJ2LS
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
from pick import pick
|
||||||
|
import time
|
||||||
|
import sounddevice as sd
|
||||||
|
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||||
|
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
|
||||||
|
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
|
||||||
|
args = parser.parse_args()
|
||||||
|
HOST, PORT = args.socket_host, args.socket_port
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
# Connect to server
|
||||||
|
sock.connect((HOST, PORT))
|
||||||
|
|
||||||
|
|
||||||
|
def main_menu():
|
||||||
|
while True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
title = 'Please select a command you want to run: '
|
||||||
|
options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES']
|
||||||
|
option, index = pick(options, title)
|
||||||
|
|
||||||
|
# BEACON AREA
|
||||||
|
if option == 'BEACON':
|
||||||
|
option, index = pick(['5',
|
||||||
|
'10',
|
||||||
|
'15',
|
||||||
|
'30',
|
||||||
|
'45',
|
||||||
|
'60',
|
||||||
|
'90',
|
||||||
|
'120',
|
||||||
|
'300',
|
||||||
|
'600',
|
||||||
|
'900',
|
||||||
|
'1800',
|
||||||
|
'3600',
|
||||||
|
'STOP BEACON',
|
||||||
|
'----- BACK -----'], "Select beacon interval [seconds]")
|
||||||
|
|
||||||
|
if option == '----- BACK -----':
|
||||||
|
main_menu()
|
||||||
|
elif option == 'STOP BEACON':
|
||||||
|
run_network_command({"type": "broadcast", "command": "stop_beacon"})
|
||||||
|
|
||||||
|
else:
|
||||||
|
run_network_command({"type": "broadcast", "command": "start_beacon", "parameter": str(option)})
|
||||||
|
|
||||||
|
elif option == 'PING':
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif option == 'ARQ':
|
||||||
|
|
||||||
|
option, index = pick(['GET RX BUFFER', 'DISCONNECT', '----- BACK -----'], "Select ARQ command")
|
||||||
|
|
||||||
|
if option == '----- BACK -----':
|
||||||
|
main_menu()
|
||||||
|
elif option == 'GET RX BUFFER':
|
||||||
|
run_network_command({"type": "get", "command": "rx_buffer"})
|
||||||
|
else:
|
||||||
|
run_network_command({"type": "arq", "command": "disconnect"})
|
||||||
|
|
||||||
|
elif option == 'LIST AUDIO DEVICES':
|
||||||
|
|
||||||
|
devices = sd.query_devices(device=None, kind=None)
|
||||||
|
device_list = []
|
||||||
|
for device in devices:
|
||||||
|
device_list.append(
|
||||||
|
f"{device['index']} - "
|
||||||
|
f"{sd.query_hostapis(device['hostapi'])['name']} - "
|
||||||
|
f"Channels (In/Out):{device['max_input_channels']}/{device['max_output_channels']} - "
|
||||||
|
f"{device['name']}")
|
||||||
|
|
||||||
|
device_list.append('----- BACK -----')
|
||||||
|
|
||||||
|
option, index = pick(device_list, "Audio devices")
|
||||||
|
|
||||||
|
if option == '----- BACK -----':
|
||||||
|
main_menu()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("no menu point found...")
|
||||||
|
|
||||||
|
|
||||||
|
def run_network_command(command):
|
||||||
|
command = json.dumps(command)
|
||||||
|
command = bytes(command + "\n", 'utf-8')
|
||||||
|
sock.sendall(command)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main_menu()
|
114
tools/freedata_network_listener.py
Executable file
114
tools/freedata_network_listener.py
Executable file
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
daemon.py
|
||||||
|
|
||||||
|
Author: DJ2LS, January 2022
|
||||||
|
|
||||||
|
daemon for providing basic information for the tnc like audio or serial devices
|
||||||
|
|
||||||
|
"""
|
||||||
|
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import structlog
|
||||||
|
import queue
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
|
||||||
|
log = structlog.get_logger("CLIENT")
|
||||||
|
|
||||||
|
|
||||||
|
split_char = b"\x00;"
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||||
|
parser.add_argument('--port', dest="socket_port", default=3000, help="Set the port, the socket is listening on.", type=int)
|
||||||
|
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ip, port = args.socket_host, args.socket_port
|
||||||
|
connected = True
|
||||||
|
data = bytes()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Nachricht
|
||||||
|
{'command': 'rx_buffer', 'data-array': [{'uuid': '8dde227d-3a09-4f39-b34c-5f8281d719d1', 'timestamp': 1672043316, 'dxcallsign': 'DJ2LS-1', 'dxgrid': 'JN48cs', 'data': 'bQA7c2VuZF9tZXNzYWdlADsxMjMAO2VkY2NjZDAyLTUzMTQtNDc3Ni1hMjlkLTFmY2M1ZDI4OTM4ZAA7VGVzdAoAOwA7cGxhaW4vdGV4dAA7ADsxNjcyMDQzMzA5'}]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decode_and_save_data(encoded_data):
|
||||||
|
decoded_data = base64.b64decode(encoded_data)
|
||||||
|
decoded_data = decoded_data.split(split_char)
|
||||||
|
|
||||||
|
if decoded_data[0] == b'm':
|
||||||
|
print(jsondata)
|
||||||
|
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", uuid=decoded_data[3])
|
||||||
|
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", message=decoded_data[4])
|
||||||
|
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", filename=decoded_data[5])
|
||||||
|
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", filetype=decoded_data[6])
|
||||||
|
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", data=decoded_data[7])
|
||||||
|
|
||||||
|
try:
|
||||||
|
folderpath = "received"
|
||||||
|
if not os.path.exists(folderpath):
|
||||||
|
os.makedirs(folderpath)
|
||||||
|
filename = decoded_data[8].decode("utf-8") + "_" + decoded_data[5].decode("utf-8")
|
||||||
|
|
||||||
|
with open(f"{folderpath}/{filename}", "wb") as file:
|
||||||
|
file.write(decoded_data[7])
|
||||||
|
|
||||||
|
with open(f"{folderpath}/{decoded_data[8].decode('utf-8')}_msg.txt", "wb") as file:
|
||||||
|
file.write(decoded_data[4])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(decoded_data)
|
||||||
|
|
||||||
|
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
|
sock.connect((ip, port))
|
||||||
|
print(sock)
|
||||||
|
while connected:
|
||||||
|
chunk = sock.recv(1024)
|
||||||
|
data += chunk
|
||||||
|
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 through data list
|
||||||
|
for command in data:
|
||||||
|
|
||||||
|
jsondata = json.loads(command)
|
||||||
|
|
||||||
|
if jsondata.get('command') == "tnc_state":
|
||||||
|
pass
|
||||||
|
|
||||||
|
if jsondata.get('freedata') == "tnc-message":
|
||||||
|
log.info(jsondata)
|
||||||
|
|
||||||
|
if jsondata.get('ping') == "acknowledge":
|
||||||
|
log.info(f"PING {jsondata.get('mycallsign')} >><< {jsondata.get('dxcallsign')}", snr=jsondata.get('snr'), dxsnr=jsondata.get('dxsnr'))
|
||||||
|
|
||||||
|
if jsondata.get('status') == 'receiving':
|
||||||
|
log.info(jsondata)
|
||||||
|
|
||||||
|
if jsondata.get('command') == 'rx_buffer':
|
||||||
|
for rxdata in jsondata["data-array"]:
|
||||||
|
log.info(f"rx buffer {rxdata.get('uuid')}")
|
||||||
|
decode_and_save_data(rxdata.get('data'))
|
||||||
|
|
||||||
|
if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission':
|
||||||
|
decode_and_save_data(jsondata["data"])
|
||||||
|
|
||||||
|
# clear data buffer as soon as data has been read
|
||||||
|
data = bytes()
|
||||||
|
|
||||||
|
|
117
tools/send_file.py
Executable file
117
tools/send_file.py
Executable file
|
@ -0,0 +1,117 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
|
||||||
|
@author: DJ2LS
|
||||||
|
python3 send_file.py --file cleanup.sh --dxcallsign DN2LS-0 --mycallsign DN2LS-2 --attempts 3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import time
|
||||||
|
import crcengine
|
||||||
|
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||||
|
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
|
||||||
|
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
|
||||||
|
parser.add_argument('--file', dest="filename", default='', help="Select the file we want to send", type=str)
|
||||||
|
parser.add_argument('--msg', dest="chatmessage", default='file from cli tool', help="Additional text message appended to file", type=str)
|
||||||
|
|
||||||
|
parser.add_argument('--dxcallsign', dest="dxcallsign", default='AA0AA', help="Select the destination callsign", type=str)
|
||||||
|
parser.add_argument('--mycallsign', dest="mycallsign", default='AA0AA', help="Select the own callsign", type=str)
|
||||||
|
parser.add_argument('--attempts', dest="attempts", default='5', help="Amount of connection attempts", type=int)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
HOST, PORT = args.socket_host, args.socket_port
|
||||||
|
filename = args.filename
|
||||||
|
dxcallsign = args.dxcallsign
|
||||||
|
mycallsign = args.mycallsign
|
||||||
|
attempts = args.attempts
|
||||||
|
chatmessage = bytes(args.chatmessage, "utf-8")
|
||||||
|
|
||||||
|
if filename != "":
|
||||||
|
# open file by name
|
||||||
|
f = open(filename, "rb")
|
||||||
|
file = f.read()
|
||||||
|
filename = bytes(filename, "utf-8")
|
||||||
|
|
||||||
|
else:
|
||||||
|
file = b""
|
||||||
|
filename = b""
|
||||||
|
|
||||||
|
# convert binary data to base64
|
||||||
|
#base64_data = base64.b64encode(file).decode("UTF-8")
|
||||||
|
split_char = b'\0;\1;'
|
||||||
|
|
||||||
|
filetype = b"unknown"
|
||||||
|
timestamp = str(int(time.time()))
|
||||||
|
|
||||||
|
# timestamp = timestamp.to_bytes(4, byteorder="big")
|
||||||
|
timestamp = bytes(timestamp, "utf-8")
|
||||||
|
msg_with_attachment = timestamp + \
|
||||||
|
split_char + \
|
||||||
|
chatmessage + \
|
||||||
|
split_char + \
|
||||||
|
filename + \
|
||||||
|
split_char + \
|
||||||
|
filetype + \
|
||||||
|
split_char + \
|
||||||
|
file
|
||||||
|
|
||||||
|
# calculate checksum
|
||||||
|
crc_algorithm = crcengine.new("crc32") # load crc32 library
|
||||||
|
crc_data = crc_algorithm(file)
|
||||||
|
crc_data = crc_data.to_bytes(4, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
|
datatype = b"m"
|
||||||
|
command = b"send_message"
|
||||||
|
checksum = bytes(crc_data.hex(), "utf-8")
|
||||||
|
uuid_4 = bytes(str(uuid.uuid4()), "utf-8")
|
||||||
|
|
||||||
|
data = datatype + \
|
||||||
|
split_char + \
|
||||||
|
command + \
|
||||||
|
split_char + \
|
||||||
|
checksum + \
|
||||||
|
split_char + \
|
||||||
|
uuid_4 + \
|
||||||
|
split_char + \
|
||||||
|
msg_with_attachment
|
||||||
|
data = base64.b64encode(data).decode("UTF-8")
|
||||||
|
|
||||||
|
# message
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# our command we are going to send
|
||||||
|
command = {"type": "arq",
|
||||||
|
"command": "send_raw",
|
||||||
|
"parameter":
|
||||||
|
[{"dxcallsign": dxcallsign,
|
||||||
|
"mycallsign": mycallsign,
|
||||||
|
"attempts": str(attempts),
|
||||||
|
"mode": "255",
|
||||||
|
"n_frames": "1",
|
||||||
|
"data": data}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
command = json.dumps(command)
|
||||||
|
print(command)
|
||||||
|
command = bytes(command + "\n", 'utf-8')
|
||||||
|
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
|
# Connect to server and send data
|
||||||
|
sock.connect((HOST, PORT))
|
||||||
|
sock.sendall(command)
|
||||||
|
timeout = time.time() + 5
|
||||||
|
while time.time() < timeout:
|
||||||
|
pass
|
47
tools/send_ping_cq.py
Normal file
47
tools/send_ping_cq.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
|
||||||
|
@author: DJ2LS
|
||||||
|
python3 send_file.py --file cleanup.sh --dxcallsign DN2LS-0 --mycallsign DN2LS-2 --attempts 3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
|
||||||
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
|
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||||
|
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
|
||||||
|
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
|
||||||
|
parser.add_argument('--dxcallsign', dest="dxcallsign", default='AA0AA', help="Select the destination callsign", type=str)
|
||||||
|
parser.add_argument('--mycallsign', dest="mycallsign", default='AA0AA', help="Select the own callsign", type=str)
|
||||||
|
parser.add_argument('--ping', dest="ping", action="store_true", help="Send PING")
|
||||||
|
parser.add_argument('--cq', dest="cq", action="store_true", help="Send CQ")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
HOST, PORT = args.socket_host, args.socket_port
|
||||||
|
dxcallsign = args.dxcallsign
|
||||||
|
mycallsign = args.mycallsign
|
||||||
|
|
||||||
|
# our command we are going to send
|
||||||
|
if args.ping:
|
||||||
|
command = {"type": "ping",
|
||||||
|
"command": "ping",
|
||||||
|
"dxcallsign": dxcallsign,
|
||||||
|
"mycallsign": mycallsign,
|
||||||
|
}
|
||||||
|
if args.cq:
|
||||||
|
command = {"type": "broadcast",
|
||||||
|
"command": "cqcqcq",
|
||||||
|
"mycallsign": mycallsign,
|
||||||
|
}
|
||||||
|
command = json.dumps(command)
|
||||||
|
command = bytes(command + "\n", 'utf-8')
|
||||||
|
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
|
# Connect to server and send data
|
||||||
|
sock.connect((HOST, PORT))
|
||||||
|
sock.sendall(command)
|
Loading…
Reference in a new issue