mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Compare commits
452 commits
a9c415c59e
...
8c6e9f44c5
Author | SHA1 | Date | |
---|---|---|---|
|
8c6e9f44c5 | ||
|
499416ef1e | ||
|
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 | ||
|
ec7519400a | ||
|
5099981472 | ||
|
ab71834b4e | ||
|
df52b15e0f | ||
|
d03b15b99e | ||
|
9295b8e8ed | ||
|
bb1e277888 | ||
|
93e5d301cd | ||
|
f3defd8600 | ||
|
65cfd52d81 | ||
|
b57fb299b4 | ||
|
a25ff279ad | ||
|
9314e680c9 | ||
|
f8b551ceb1 | ||
|
af1fc99e23 | ||
|
ae12ed660a | ||
|
1c6ac43db5 | ||
|
4cf35381c0 | ||
|
f4c120bc37 | ||
|
e1d4bfa899 | ||
|
18c53329ee | ||
|
ff3ee7b3cf | ||
|
f426964bdf | ||
|
65cab5fa3c | ||
|
1c00fcd13b | ||
|
bf1c630977 | ||
|
f9b0fc7da8 | ||
|
8e6e464bfc | ||
|
48781ba208 | ||
|
cb2d9907c8 | ||
|
1588031bfa | ||
|
a322e949c0 | ||
|
271f33003c | ||
|
0d49340d7e | ||
|
cb8b81e5a7 | ||
|
b5708b2e01 | ||
|
6c4a238528 | ||
|
a9cece098d | ||
|
0af386ec20 | ||
|
93f90f2345 | ||
|
08de279787 | ||
|
93a39ce84f | ||
|
1d8520666d | ||
|
fa3efd6492 | ||
|
1279ef20e0 | ||
|
71c49c1336 | ||
|
e9d0236c9d | ||
|
d519052ce9 | ||
|
aab4fdf17f | ||
|
1811e4a02e | ||
|
4c877357f7 | ||
|
33e0f61675 | ||
|
bd198d7c67 | ||
|
166bb6aba4 | ||
|
41d3c7fc82 | ||
|
1342152edd | ||
|
a901944268 | ||
|
21076ef87f | ||
|
77f7312f97 | ||
|
6f3e6c698c | ||
|
d3f64ec60d | ||
|
89369f8208 | ||
|
ccff03705d | ||
|
ea599f51e3 | ||
|
a8a643f15a | ||
|
38281ec06c | ||
|
03987de194 | ||
|
8f0dd9df8b | ||
|
6851d2c427 | ||
|
87c08652b8 | ||
|
1b75947581 | ||
|
cf1f8aa078 | ||
|
8e2fb9aea3 | ||
|
2a4f6c950f | ||
|
f3bdddd644 | ||
|
5e91a874f0 | ||
|
0e2d23c5b9 | ||
|
85d2f36e36 | ||
|
8913f81b79 | ||
|
55e87840f8 | ||
|
18d18bd901 | ||
|
d3ca7dbf6c | ||
|
4c18a843fc | ||
|
8b9cd83c71 | ||
|
5774c642ec | ||
|
70bdd59847 | ||
|
bf144dc6ad | ||
|
ffe249da3f | ||
|
b29df86339 | ||
|
5f6ee51fac | ||
|
119c568ebe | ||
|
7d99f89911 | ||
|
5a1eb7a8b8 | ||
|
ce32d589ab | ||
|
503fa52696 | ||
|
d71fb3c640 | ||
|
0cafb600b3 | ||
|
cd9b8e5173 | ||
|
0326cf612a | ||
|
92bca8d4da | ||
|
2534604616 | ||
|
d843e716b7 | ||
|
8200af1d99 | ||
|
1a749c0c3d | ||
|
b284ac2139 | ||
|
aaaa0a720f | ||
|
ed31c8da18 | ||
|
53548c966d | ||
|
0831517b40 | ||
|
6db1c2fd33 | ||
|
61c813f141 | ||
|
393a541b01 | ||
|
51f70b27d6 | ||
|
2c183af8c8 | ||
|
ec9647a1d8 | ||
|
9a2711fe3f | ||
|
5de436e4f3 | ||
|
b761471ec3 | ||
|
e55623e0ae | ||
|
db0f472f43 | ||
|
553013cbb4 | ||
|
508fbe05af | ||
|
e30ea05671 | ||
|
93994610a8 | ||
|
0f6685b0e2 | ||
|
a6335fbb99 | ||
|
5e2667c438 | ||
|
cb8ee299b5 | ||
|
3e52f0f0f3 | ||
|
871c6c1756 | ||
|
232c10bf5f | ||
|
0fdcc40511 | ||
|
3afca6a5ab | ||
|
9c0e27e478 | ||
|
4d9730240f | ||
|
71d75584ee | ||
|
77bfe28e2d | ||
|
3c0e0b150d | ||
|
37c69031da | ||
|
ce69f2c34d | ||
|
1314140454 | ||
|
576c9f94f8 | ||
|
33e6a6784a | ||
|
fadd3d9b78 | ||
|
97183ecd8e | ||
|
1a3188fc71 | ||
|
89e58ca98d | ||
|
30c1844ce4 | ||
|
fc876c014a | ||
|
826a39ca4d | ||
|
a69badc88c | ||
|
aaea0d1509 | ||
|
58dd443325 | ||
|
6bb9932ce6 | ||
|
65dde27e6b | ||
|
58e44a2dbb | ||
|
4ce36dba5e | ||
|
eea9ff9919 | ||
|
5c041161e6 | ||
|
031e353627 | ||
|
b3726cfae5 | ||
|
0e28e2e3a2 | ||
|
5acdc338ba | ||
|
7b0535193d | ||
|
9e312b3b3a | ||
|
399177eca3 | ||
|
0a30f3fd2c | ||
|
cf25cadc3c | ||
|
4b37ea4d67 | ||
|
616fb214d2 | ||
|
5553009d74 | ||
|
392e0bf930 | ||
|
7d2168a0e7 | ||
|
b41430fc43 | ||
|
f096c7f3f9 | ||
|
d98358b6ce | ||
|
dee94b0acb | ||
|
cdb12861a5 | ||
|
3600516a0b | ||
|
b0e3d2286e | ||
|
101903b924 | ||
|
dad9230f3d | ||
|
d365255a95 | ||
|
d865edcfe7 | ||
|
47f6e54b6d | ||
|
1db840e1f4 | ||
|
2fe38cf0e8 | ||
|
28df25c1fa | ||
|
22999bfba3 | ||
|
775bcc0015 | ||
|
54285c1c69 | ||
|
15e483a233 | ||
|
56ee05186c | ||
|
2ed79df2be | ||
|
0ab0f444a2 | ||
|
7a682219cc | ||
|
5a8a563787 | ||
|
3d044189ac | ||
|
5acaed0e7e | ||
|
7fbae90e81 | ||
|
35a45ab641 | ||
|
3ef3af88e1 | ||
|
9045746466 | ||
|
19b18d0719 |
73 changed files with 4365 additions and 4691 deletions
231
.github/workflows/build_multiplatform.yml
vendored
231
.github/workflows/build_multiplatform.yml
vendored
|
@ -100,9 +100,6 @@ jobs:
|
|||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
path: codec2/tempfiles/*
|
||||
|
||||
|
||||
|
||||
|
||||
BUILD_ARM:
|
||||
# The host should always be linux
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -199,6 +196,7 @@ jobs:
|
|||
name: Build FreeDATA packages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-11, windows-latest]
|
||||
include:
|
||||
|
@ -233,84 +231,71 @@ jobs:
|
|||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Create tnc/dist
|
||||
working-directory: tnc
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
- name: Download libcodec2 artifact
|
||||
- name: Create tnc/dist/tnc
|
||||
working-directory: tnc
|
||||
run: |
|
||||
mkdir -p dist/tnc
|
||||
|
||||
##- name: Download libcodec2 artifact TNC DIST
|
||||
## uses: actions/download-artifact@v3
|
||||
## with:
|
||||
## path: tnc/dist/codec2
|
||||
|
||||
- name: create tnc/lib/codec2
|
||||
working-directory: tnc/lib/
|
||||
run: |
|
||||
mkdir codec2
|
||||
|
||||
- name: Download libcodec2 artifact TNC LIB
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/dist/codec2
|
||||
path: tnc/lib/codec2
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
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
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
#brew install virtualenv
|
||||
#virtualenv -p python3 venv
|
||||
#cd venv
|
||||
#source ./bin/activate
|
||||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install psutil
|
||||
pip install crcengine
|
||||
# pip install 'pyinstaller<5.1'
|
||||
pip install ujson
|
||||
pip install pyserial
|
||||
pip install numpy
|
||||
pip install structlog
|
||||
pip install colorama
|
||||
pip install sounddevice
|
||||
# curl.exe --output PyAudio-0.2.11-cp39-cp39-win_amd64.whl --url https://download.lfd.uci.edu/pythonlibs/y2rycu7g/PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
pip install nuitka
|
||||
pip install pyinstaller
|
||||
pip install ordered-set
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
# - name: Install Pyaudio Windows
|
||||
# if: ${{startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: tnc/lib/pyaudio/windows
|
||||
# run: |
|
||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
|
||||
# - name: Display structure of downloaded files
|
||||
# run: ls -R
|
||||
|
||||
# - name: cleanup codec2
|
||||
# working-directory: tnc/lib/
|
||||
# run: |
|
||||
# mkdir codec2
|
||||
# cd codec2
|
||||
|
||||
# - uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# path: tnc/lib/codec2
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
- 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: Build binaries macOS
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
#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
|
||||
pyinstaller freedata.spec
|
||||
cp -r -f dist/tnc/* dist/
|
||||
rm -r dist/tnc
|
||||
# now build tnc binaries
|
||||
pyinstaller -y freedata.spec
|
||||
# and to some final cleanup
|
||||
# cp -r -f dist/tnc/* dist/
|
||||
# rm -r dist/tnc
|
||||
|
||||
- name: Build binaries Linux and Windows
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
|
@ -322,104 +307,53 @@ 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 main.py
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Copy binaries - Linux
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
cp -r -f daemon.dist/* dist/
|
||||
cp -r -f main.dist/* dist/
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
cp -r -f daemon.dist/* dist/tnc
|
||||
cp -r -f main.dist/* dist/tnc
|
||||
|
||||
- name: Copy binaries - Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
# These are powershell aliases, not UNIX commands.
|
||||
run: |
|
||||
cp -r -Force daemon.dist/* dist/
|
||||
cp -r -Force main.dist/* dist/
|
||||
cp -r -Force daemon.dist/* dist/tnc
|
||||
cp -r -Force main.dist/* dist/tnc
|
||||
|
||||
- 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
|
||||
run: |
|
||||
mv dist/daemon* dist/${{ matrix.daemon_binary_name }}
|
||||
mv dist/main* dist/${{ matrix.tnc_binary_name }}
|
||||
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
||||
mv dist/tnc/main* dist/tnc/${{ matrix.tnc_binary_name }}
|
||||
|
||||
- name: Download Portaudio binaries Linux macOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tnc/dist/tnc
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Download Portaudio binaries
|
||||
working-directory: tnc
|
||||
run: |
|
||||
if ! test -d "dist/_sounddevice_data"; then
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/_sounddevice_data/portaudio-binaries
|
||||
if !test -d "dist/_sounddevice_data";then
|
||||
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
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
if(Test-Path -Path "dist/_sounddevice_data"){
|
||||
echo "already exists"
|
||||
} else {
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/_sounddevice_data/portaudio-binaries
|
||||
}
|
||||
#- uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# path: tnc/dist/codec2
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
#- name: Compress TNC
|
||||
# # if: ${{!startsWith(matrix.os, 'windows') }}
|
||||
# shell: bash
|
||||
# run: |
|
||||
# cd ./tnc/dist
|
||||
# zip -r ./${{ matrix.zip_name }}.zip *
|
||||
|
||||
- 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 }}.zip'
|
||||
# directory: ./tnc/dist/tnc
|
||||
directory: ./tnc/dist
|
||||
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: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Release TNC
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
# files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
files: ./tnc/dist/${{ matrix.zip_name }}.zip
|
||||
ls -l
|
||||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
|
@ -430,13 +364,40 @@ jobs:
|
|||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
args: ${{ matrix.electron_parameters }}
|
||||
max_attempts: 3
|
||||
|
||||
- name: Compress TNC
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
# directory: ./tnc/dist/tnc
|
||||
directory: ./tnc/dist/tnc
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- 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: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Upload App bundle artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: app_bundle_${{ matrix.os }}.zip
|
||||
# path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
path: ./gui/dist/*
|
||||
#- name: Upload TNC artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: ${{ matrix.zip_name }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip#
|
||||
|
||||
#- name: Upload App bundle artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: app_bundle_${{ matrix.os }}.zip
|
||||
# # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
# path: ./gui/dist/*
|
||||
|
|
23
.github/workflows/ctest.yml
vendored
23
.github/workflows/ctest.yml
vendored
|
@ -9,16 +9,35 @@ jobs:
|
|||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# By default, GitHub will maximize the number of jobs run in parallel
|
||||
# depending on the available runners on GitHub-hosted virtual machines.
|
||||
# max-parallel: 8
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- python-version: "3.7"
|
||||
- python-version: "3.8"
|
||||
- python-version: "3.9"
|
||||
- python-version: "3.10"
|
||||
- python-version: "3.11"
|
||||
- python-version: "3.12-dev"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pyaudio
|
||||
pip3 install pytest pytest-rerunfailures
|
||||
|
||||
- name: Build codec2
|
||||
|
|
|
@ -50,12 +50,13 @@ add_test(NAME tnc_irs_iss
|
|||
python3 test_tnc.py")
|
||||
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME chat_text
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../tnc;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_chat_text.py")
|
||||
set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
# disabled this test as its actually broken since we entroduced session IDs
|
||||
#add_test(NAME chat_text
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../tnc;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_chat_text.py")
|
||||
# set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME datac0_frames
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
|
|
|
@ -21,9 +21,8 @@ Please keep in mind, this project is still under development with many issues wh
|
|||
- [x] SNR operation level SNR > 0dB MPP/MPD
|
||||
- [x] file compression
|
||||
- [x] auto updater
|
||||
- [ ] channel measurement
|
||||
- [x] channel measurement
|
||||
- [ ] hybrid ARQ
|
||||
- [ ] SNR operation level SNR @ -5dB MPP/MPD
|
||||
- [ ] tbc...
|
||||
### existing/planned Chat features
|
||||
- [x] chat messages
|
||||
|
|
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>
|
BIN
gui/build/icon.png
Normal file
BIN
gui/build/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 590 KiB |
|
@ -20,6 +20,10 @@ var socketchunk = ''; // Current message, per connection.
|
|||
// global to keep track of daemon connection error emissions
|
||||
var daemonShowConnectStateError = 1
|
||||
|
||||
// global for storing ip information
|
||||
var daemon_port = config.daemon_port;
|
||||
var daemon_host = config.daemon_host;
|
||||
|
||||
setTimeout(connectDAEMON, 500)
|
||||
|
||||
function connectDAEMON() {
|
||||
|
@ -27,13 +31,13 @@ function connectDAEMON() {
|
|||
daemonLog.info('connecting to daemon');
|
||||
}
|
||||
|
||||
//clear message buffer after reconnecting or inital connection
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = '';
|
||||
|
||||
if (config.tnclocation == 'localhost') {
|
||||
daemon.connect(3001, '127.0.0.1')
|
||||
} else {
|
||||
daemon.connect(config.daemon_port, config.daemon_host)
|
||||
daemon.connect(daemon_port, daemon_host)
|
||||
|
||||
}
|
||||
|
||||
|
@ -217,7 +221,7 @@ exports.getDaemonState = function() {
|
|||
// START TNC
|
||||
// ` `== multi line string
|
||||
|
||||
exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, devicename, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port, enable_fft, enable_scatter, low_bandwidth_mode, tuning_range_fmin, tuning_range_fmax, enable_fsk, tx_audio_level, respond_to_cq, rx_buffer_size) {
|
||||
exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, devicename, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port, enable_fft, enable_scatter, low_bandwidth_mode, tuning_range_fmin, tuning_range_fmax, enable_fsk, tx_audio_level, respond_to_cq, rx_buffer_size, enable_explorer) {
|
||||
var json_command = JSON.stringify({
|
||||
type: 'set',
|
||||
command: 'start_tnc',
|
||||
|
@ -245,7 +249,8 @@ exports.startTNC = function(mycall, mygrid, rx_audio, tx_audio, radiocontrol, de
|
|||
tuning_range_fmax : tuning_range_fmax,
|
||||
tx_audio_level : tx_audio_level,
|
||||
respond_to_cq : respond_to_cq,
|
||||
rx_buffer_size : rx_buffer_size
|
||||
rx_buffer_size : rx_buffer_size,
|
||||
enable_explorer : enable_explorer
|
||||
}]
|
||||
})
|
||||
|
||||
|
@ -298,4 +303,19 @@ exports.saveMyGrid = function(grid) {
|
|||
writeDaemonCommand(command)
|
||||
}
|
||||
|
||||
|
||||
ipcRenderer.on('action-update-daemon-ip', (event, arg) => {
|
||||
daemon.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
daemon_port = arg.port;
|
||||
daemon_host = arg.adress;
|
||||
connectDAEMON();
|
||||
});
|
||||
|
|
167
gui/main.js
167
gui/main.js
|
@ -10,12 +10,13 @@ const path = require('path');
|
|||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const spawn = require('child_process').spawn;
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
const log = require('electron-log');
|
||||
const mainLog = log.scope('main');
|
||||
const daemonProcessLog = log.scope('freedata-daemon');
|
||||
const mime = require('mime');
|
||||
const net = require('net');
|
||||
|
||||
|
||||
const sysInfo = log.scope('system information');
|
||||
sysInfo.info("SYSTEM INFORMATION ----------------------------- ");
|
||||
|
@ -90,7 +91,9 @@ const configDefaultSettings = '{\
|
|||
"tuning_range_fmin" : "-50.0",\
|
||||
"tuning_range_fmax" : "50.0",\
|
||||
"respond_to_cq" : "True",\
|
||||
"rx_buffer_size" : "16" \
|
||||
"rx_buffer_size" : "16", \
|
||||
"enable_explorer" : "False", \
|
||||
"wftheme": 2 \
|
||||
}';
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
@ -167,14 +170,31 @@ let data = null;
|
|||
let logViewer = null;
|
||||
var daemonProcess = null;
|
||||
|
||||
|
||||
// create a splash screen
|
||||
function createSplashScreen(){
|
||||
splashScreen = new BrowserWindow({
|
||||
height: 250,
|
||||
width: 250,
|
||||
transparent: true,
|
||||
frame: false,
|
||||
alwaysOnTop: true
|
||||
});
|
||||
splashScreen.loadFile('src/splash.html');
|
||||
splashScreen.center();
|
||||
}
|
||||
|
||||
|
||||
function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: config.screen_width,
|
||||
height: config.screen_height,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
icon: 'src/img/icon.png',
|
||||
webPreferences: {
|
||||
//preload: path.join(__dirname, 'preload-main.js'),
|
||||
backgroundThrottle: false,
|
||||
preload: require.resolve('./preload-main.js'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
|
@ -283,8 +303,19 @@ function createWindow() {
|
|||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
// show splash screen
|
||||
createSplashScreen();
|
||||
|
||||
// create main window
|
||||
createWindow();
|
||||
|
||||
// wait some time, then close splash screen and show main windows
|
||||
setTimeout(function() {
|
||||
splashScreen.close();
|
||||
win.show();
|
||||
}, 3000);
|
||||
|
||||
// start daemon by checking os
|
||||
mainLog.info('Starting freedata-daemon binary');
|
||||
|
||||
|
@ -395,6 +426,16 @@ ipcMain.on('request-show-chat-window', () => {
|
|||
chat.show();
|
||||
});
|
||||
|
||||
// UPDATE TNC CONNECTION
|
||||
ipcMain.on('request-update-tnc-ip',(event,data)=>{
|
||||
win.webContents.send('action-update-tnc-ip', data);
|
||||
});
|
||||
|
||||
// UPDATE DAEMON CONNECTION
|
||||
ipcMain.on('request-update-daemon-ip',(event,data)=>{
|
||||
win.webContents.send('action-update-daemon-ip', data);
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on('request-update-tnc-state', (event, arg) => {
|
||||
win.webContents.send('action-update-tnc-state', arg);
|
||||
|
@ -519,7 +560,7 @@ ipcMain.on('save-file-to-folder',(event,data)=>{
|
|||
console.log(data.file)
|
||||
|
||||
try {
|
||||
|
||||
|
||||
let buffer = Buffer.from(data.file);
|
||||
let arraybuffer = Uint8Array.from(buffer);
|
||||
console.log(arraybuffer)
|
||||
|
@ -540,6 +581,7 @@ ipcMain.on('save-file-to-folder',(event,data)=>{
|
|||
});
|
||||
|
||||
|
||||
|
||||
//tnc messages START --------------------------------------
|
||||
|
||||
// CQ TRANSMITTING
|
||||
|
@ -592,6 +634,12 @@ ipcMain.on('request-show-arq-toast-datachannel-opening',(event,data)=>{
|
|||
win.webContents.send('action-show-arq-toast-datachannel-opening', data);
|
||||
});
|
||||
|
||||
// ARQ DATA CHANNEL WAITING
|
||||
ipcMain.on('request-show-arq-toast-datachannel-waiting',(event,data)=>{
|
||||
win.webContents.send('action-show-arq-toast-datachannel-waiting', data);
|
||||
});
|
||||
|
||||
|
||||
// ARQ DATA CHANNEL OPEN
|
||||
ipcMain.on('request-show-arq-toast-datachannel-opened',(event,data)=>{
|
||||
win.webContents.send('action-show-arq-toast-datachannel-opened', data);
|
||||
|
@ -632,6 +680,11 @@ ipcMain.on('request-show-arq-toast-session-connecting',(event,data)=>{
|
|||
win.webContents.send('action-show-arq-toast-session-connecting', data);
|
||||
});
|
||||
|
||||
// ARQ SESSION WAITING
|
||||
ipcMain.on('request-show-arq-toast-session-waiting',(event,data)=>{
|
||||
win.webContents.send('action-show-arq-toast-session-waiting', data);
|
||||
});
|
||||
|
||||
// ARQ SESSION CONNECTED
|
||||
ipcMain.on('request-show-arq-toast-session-connected',(event,data)=>{
|
||||
win.webContents.send('action-show-arq-toast-session-connected', data);
|
||||
|
@ -781,15 +834,24 @@ function close_all() {
|
|||
// RUN RIGCTLD
|
||||
ipcMain.on('request-start-rigctld',(event, data)=>{
|
||||
|
||||
|
||||
try{
|
||||
spawn(data.path, data.parameters);
|
||||
let rigctld_proc = spawn(data.path, data.parameters);
|
||||
|
||||
rigctld_proc.on('exit', function (code) {
|
||||
console.log('rigctld process exited with code ' + code);
|
||||
|
||||
// if rigctld crashes, error code is -2
|
||||
// then we are going to restart rigctld
|
||||
// this "fixes" a problem with latest rigctld on raspberry pi
|
||||
//if (code == -2){
|
||||
// setTimeout(ipcRenderer.send('request-start-rigctld', data), 500);
|
||||
//}
|
||||
//let rigctld_proc = spawn(data.path, data.parameters);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
const rigctld = exec(data.path, data.parameters);
|
||||
rigctld.stdout.on("data", data => {
|
||||
|
@ -828,53 +890,60 @@ ipcMain.on('request-stop-rigctld',(event,data)=>{
|
|||
|
||||
|
||||
|
||||
// CHECK RIGCTLD
|
||||
ipcMain.on('request-check-rigctld',(data)=>{
|
||||
try {
|
||||
// CHECK RIGCTLD CONNECTION
|
||||
// create new socket so we are not reopening every time a new one
|
||||
var rigctld_connection = new net.Socket();
|
||||
var rigctld_connection_state = false;
|
||||
ipcMain.on('request-check-rigctld',(event, data)=>{
|
||||
|
||||
try{
|
||||
|
||||
let Data = {
|
||||
state: "unknown",
|
||||
state: "unknown",
|
||||
};
|
||||
|
||||
isRunning('rigctld', (status) => {
|
||||
if (status){
|
||||
Data["state"] = "running";
|
||||
} else {
|
||||
Data["state"] = "unknown/stopped";
|
||||
if(!rigctld_connection_state){
|
||||
rigctld_connection = new net.Socket();
|
||||
rigctld_connection.connect(data.port, data.ip)
|
||||
}
|
||||
|
||||
// check if we have created a new socket object
|
||||
if (typeof(rigctld_connection) != 'undefined') {
|
||||
|
||||
rigctld_connection.on('connect', function() {
|
||||
rigctld_connection_state = true;
|
||||
Data["state"] = "connection possible - (" + data.ip + ":" + data.port + ")";
|
||||
if (win !== null && win !== '' && typeof(win) != 'undefined'){
|
||||
// try catch for being sure we have a clean app close
|
||||
try{
|
||||
win.webContents.send('action-check-rigctld', Data);
|
||||
} catch(e){
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
win.webContents.send('action-check-rigctld', Data);
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
mainLog.error(e)
|
||||
rigctld_connection.on('error', function() {
|
||||
rigctld_connection_state = false;
|
||||
Data["state"] = "unknown/stopped - (" + data.ip + ":" + data.port + ")";
|
||||
if (win !== null && win !== '' && typeof(win) != 'undefined'){
|
||||
// try catch for being sure we have a clean app close
|
||||
try{
|
||||
win.webContents.send('action-check-rigctld', Data);
|
||||
} catch(e){
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
rigctld_connection.on('end', function() {
|
||||
rigctld_connection_state = false;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/51084163
|
||||
// Function for checking if a process is running or not
|
||||
/*
|
||||
isRunning('rigctld', (status) => {
|
||||
if (status){
|
||||
Data["state"] = "running";
|
||||
} else {
|
||||
Data["state"] = "unknown";
|
||||
}
|
||||
win.webContents.send('action-check-rigctld', Data);
|
||||
})
|
||||
*/
|
||||
const isRunning = (query, cb) => {
|
||||
let platform = process.platform;
|
||||
let cmd = '';
|
||||
switch (platform) {
|
||||
case 'win32' : cmd = `tasklist`; break;
|
||||
case 'darwin' : cmd = `ps -ax | grep ${query}`; break;
|
||||
case 'linux' : cmd = `ps -A`; break;
|
||||
default: break;
|
||||
}
|
||||
exec(cmd, (err, stdout) => {
|
||||
cb(stdout.toLowerCase().indexOf(query.toLowerCase()) > -1);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.5.0-alpha.1",
|
||||
"version": "0.6.11-alpha.4",
|
||||
"description": "FreeDATA ",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
@ -28,20 +28,26 @@
|
|||
},
|
||||
"homepage": "https://freedata.app",
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.3",
|
||||
"@electron/osx-sign": "^1.0.4",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"blob-util": "^2.0.2",
|
||||
"bootstrap": "^5.2.1",
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"bootswatch": "^5.2.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"chartjs-plugin-annotation": "^2.0.1",
|
||||
"chart.js": "^4.0.0",
|
||||
"chartjs-plugin-annotation": "^2.1.2",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-updater": "^5.2.1",
|
||||
"emoji-picker-element": "^1.12.1",
|
||||
"emoji-picker-element-data": "^1.3.0",
|
||||
"express-pouchdb": "^4.2.0",
|
||||
"mime": "^3.0.0",
|
||||
"pouchdb": "^7.3.0",
|
||||
"pouchdb-browser": "^7.3.0",
|
||||
"pouchdb-express-router": "^0.0.11",
|
||||
"pouchdb-find": "^7.3.0",
|
||||
"pouchdb-replication": "^8.0.0",
|
||||
"qth-locator": "^2.1.0",
|
||||
"utf8": "^3.0.0",
|
||||
"uuid": "^9.0.0"
|
||||
|
@ -55,32 +61,28 @@
|
|||
"appId": "app.freedata",
|
||||
"npmRebuild": "false",
|
||||
"directories": {
|
||||
"buildResources": "src/img",
|
||||
"buildResources": "build",
|
||||
"output": "dist"
|
||||
},
|
||||
"dmg": {
|
||||
"icon": "src/img/icon.png",
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
"mac": {
|
||||
"target": [
|
||||
"default"
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
|
||||
"win": {
|
||||
"icon": "src/img/icon.png",
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "src/img/icon.png",
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
|
@ -91,7 +93,14 @@
|
|||
"releaseType": "release"
|
||||
},
|
||||
"extraResources": [
|
||||
"./tnc/**"
|
||||
{
|
||||
"from": "../tnc/dist/tnc/",
|
||||
"to": "tnc",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/.git"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ const dateFormatHours = new Intl.DateTimeFormat('en-GB', {
|
|||
hour12: false,
|
||||
});
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
const split_char = '\0;\1;'
|
||||
// global for our selected file we want to transmit
|
||||
// ----------------- some chat globals
|
||||
var filetype = '';
|
||||
|
@ -61,7 +61,52 @@ try{
|
|||
}
|
||||
|
||||
PouchDB.plugin(require('pouchdb-find'));
|
||||
//PouchDB.plugin(require('pouchdb-replication'));
|
||||
|
||||
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,
|
||||
retry: false
|
||||
}).on('change', function (change) {
|
||||
// yo, something changed!
|
||||
console.log(change)
|
||||
}).on('paused', function (err) {
|
||||
// replication was paused, usually because of a lost connection
|
||||
console.log(err)
|
||||
}).on('active', function (info) {
|
||||
// replication was resumed
|
||||
console.log(info)
|
||||
}).on('error', function (err) {
|
||||
// totally unhandled error (shouldn't happen)
|
||||
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();
|
||||
db.createIndex({
|
||||
index: {
|
||||
|
@ -73,6 +118,7 @@ db.createIndex({
|
|||
}).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
db.find({
|
||||
selector: {
|
||||
timestamp: {
|
||||
|
@ -87,7 +133,15 @@ db.find({
|
|||
if (typeof(result) !== 'undefined') {
|
||||
result.docs.forEach(function(item) {
|
||||
//console.log(item)
|
||||
update_chat(item);
|
||||
// another query with attachments
|
||||
db.get(item._id, {
|
||||
attachments: true
|
||||
}).then(function(item_with_attachments){
|
||||
update_chat(item_with_attachments);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}).catch(function(err) {
|
||||
|
@ -174,18 +228,49 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||
document.getElementById("chatModuleMessage").addEventListener("input", () => {
|
||||
var textarea = document.getElementById("chatModuleMessage");
|
||||
var text = textarea.value;
|
||||
|
||||
if(document.getElementById("expand_textarea").checked){
|
||||
var lines = 6
|
||||
} else {
|
||||
var lines = text.split("\n").length
|
||||
if (lines >= 10){
|
||||
lines = 10;
|
||||
|
||||
if (lines >= 6){
|
||||
lines = 6;
|
||||
}
|
||||
var message_container_height_offset = 90 + (23*lines);
|
||||
|
||||
}
|
||||
var message_container_height_offset = 130 + (20*lines);
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height = message_container_height;
|
||||
textarea.rows = lines;
|
||||
|
||||
console.log(textarea.value)
|
||||
|
||||
|
||||
})
|
||||
|
||||
document.getElementById("expand_textarea").addEventListener("click", () => {
|
||||
var textarea = document.getElementById("chatModuleMessage");
|
||||
|
||||
if(document.getElementById("expand_textarea").checked){
|
||||
var lines=6
|
||||
document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-down";
|
||||
|
||||
} else {
|
||||
var lines=1
|
||||
document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-up";
|
||||
}
|
||||
|
||||
var message_container_height_offset = 130 + (20*lines);
|
||||
//var message_container_height_offset = 90 + (23*lines);
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height = message_container_height;
|
||||
textarea.rows = lines;
|
||||
console.log(textarea.rows)
|
||||
|
||||
})
|
||||
|
||||
|
||||
// NEW CHAT
|
||||
|
||||
document.getElementById("createNewChatButton").addEventListener("click", () => {
|
||||
|
@ -223,10 +308,12 @@ db.post({
|
|||
var chatmessage = textarea.value;
|
||||
|
||||
// reset textarea size
|
||||
var message_container_height_offset = 110;
|
||||
var message_container_height_offset = 150;
|
||||
var message_container_height = `calc(100% - ${message_container_height_offset}px)`;
|
||||
document.getElementById("message-container").style.height = message_container_height;
|
||||
textarea.rows = 1
|
||||
document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-up";
|
||||
document.getElementById("expand_textarea").checked = false;
|
||||
|
||||
console.log(file);
|
||||
console.log(filename);
|
||||
|
@ -234,9 +321,12 @@ db.post({
|
|||
if (filetype == ''){
|
||||
filetype = 'plain/text'
|
||||
}
|
||||
var data_with_attachment = chatmessage + split_char + filename + split_char + filetype + split_char + file;
|
||||
var timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
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 = ``;
|
||||
var uuid = uuidv4();
|
||||
console.log(data_with_attachment)
|
||||
|
@ -246,17 +336,17 @@ db.post({
|
|||
mode: 255,
|
||||
frames: 1,
|
||||
data: data_with_attachment,
|
||||
checksum: '123',
|
||||
checksum: file_checksum,
|
||||
uuid: uuid
|
||||
};
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
db.post({
|
||||
_id: uuid,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
timestamp: timestamp,
|
||||
dxcallsign: dxcallsign,
|
||||
dxgrid: 'null',
|
||||
msg: chatmessage,
|
||||
checksum: 'null',
|
||||
checksum: file_checksum,
|
||||
type: "transmit",
|
||||
status: 'transmit',
|
||||
uuid: uuid,
|
||||
|
@ -342,7 +432,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
|
||||
//handle ping
|
||||
if (item.ping == 'received') {
|
||||
obj.timestamp = item.timestamp;
|
||||
obj.timestamp = parseInt(item.timestamp);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.uuid = item.uuid;
|
||||
|
@ -359,12 +449,9 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
add_obj_to_database(obj)
|
||||
update_chat_obj_by_uuid(obj.uuid);
|
||||
|
||||
|
||||
|
||||
|
||||
// handle beacon
|
||||
} else if (item.beacon == 'received') {
|
||||
obj.timestamp = item.timestamp;
|
||||
obj.timestamp = parseInt(item.timestamp);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.uuid = item.uuid;
|
||||
|
@ -386,20 +473,23 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
} else if (item.arq == 'transmission' && item.status == 'received') {
|
||||
var encoded_data = atob(item.data);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
obj.timestamp = item.timestamp;
|
||||
|
||||
console.log(splitted_data)
|
||||
|
||||
obj.timestamp = parseInt(splitted_data[4]);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.command = splitted_data[1];
|
||||
obj.checksum = splitted_data[2];
|
||||
// convert message to unicode from utf8 because of emojis
|
||||
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.snr = 'null';
|
||||
obj.type = 'received';
|
||||
obj.filename = utf8.decode(splitted_data[5]);
|
||||
obj.filetype = utf8.decode(splitted_data[6]);
|
||||
obj.file = btoa(utf8.decode(splitted_data[7]));
|
||||
obj.filename = utf8.decode(splitted_data[6]);
|
||||
obj.filetype = utf8.decode(splitted_data[7]);
|
||||
obj.file = btoa(splitted_data[8]);
|
||||
|
||||
add_obj_to_database(obj);
|
||||
update_chat_obj_by_uuid(obj.uuid);
|
||||
|
@ -437,17 +527,42 @@ update_chat = function(obj) {
|
|||
var filename = Object.keys(obj._attachments)[0]
|
||||
var filetype = filename.split('.')[1]
|
||||
var filesize = obj._attachments[filename]["length"] + " Bytes";
|
||||
|
||||
if (filesize == 'undefined Bytes'){
|
||||
// get filesize of new submitted data
|
||||
// not that nice....
|
||||
// we really should avoid converting back from base64 for performance reasons...
|
||||
var filesize = Math.ceil(atob(obj._attachments[filename]["data"]).length) + "Bytes";
|
||||
}
|
||||
|
||||
// check if image, then display it
|
||||
if(filetype == 'image/png' || filetype =="png"){
|
||||
var fileheader = `
|
||||
<div class="card-header border-0 bg-transparent text-end p-0 mb-0 hover-overlay">
|
||||
<img class="w-100 rounded-2" src="data:image/png;base64,${obj._attachments[filename]["data"]}">
|
||||
<p class="text-right mb-0 p-1 text-black" style="text-align: right; font-size : 1rem">
|
||||
<span class="p-1" style="text-align: right; font-size : 0.8rem">${filename}</span>
|
||||
<span class="p-1" style="text-align: right; font-size : 0.8rem">${filesize}</span>
|
||||
<i class="bi bi-filetype-${filetype}" style="font-size: 2rem;"></i>
|
||||
</p>
|
||||
</div>
|
||||
<hr class="m-0 p-0">
|
||||
`;
|
||||
|
||||
}else{
|
||||
|
||||
var fileheader = `
|
||||
<div class="card-header border-0 bg-transparent text-end p-0 mb-0 hover-overlay">
|
||||
<p class="text-right mb-0 p-1 text-black" style="text-align: right; font-size : 1rem">
|
||||
<span class="p-1" style="text-align: right; font-size : 0.8rem">${filename}</span>
|
||||
<span class="p-1" style="text-align: right; font-size : 0.8rem">${filesize}</span>
|
||||
<i class="bi bi-filetype-${filetype}" style="font-size: 2rem;"></i>
|
||||
<i class="bi bi-filetype-${filetype}" style="font-size: 2rem;"></i>
|
||||
</p>
|
||||
</div>
|
||||
<hr class="m-0 p-0">
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var controlarea_transmit = `
|
||||
<div class="ms-auto" id="msg-${obj._id}-control-area">
|
||||
|
@ -580,7 +695,7 @@ update_chat = function(obj) {
|
|||
<div class="card border-light bg-light" id="msg-${obj._id}">
|
||||
${fileheader}
|
||||
|
||||
<div class="card-body p-0">
|
||||
<div class="card-body rounded-3 p-0">
|
||||
<p class="card-text p-2 mb-0 text-break text-wrap">${message_html}</p>
|
||||
<p class="text-right mb-0 p-1 text-white" style="text-align: left; font-size : 0.9rem">
|
||||
<span class="badge bg-light text-muted">${timestamp}</span>
|
||||
|
@ -621,7 +736,7 @@ update_chat = function(obj) {
|
|||
<div class="card border-primary bg-primary" id="msg-${obj._id}">
|
||||
${fileheader}
|
||||
|
||||
<div class="card-body p-0 text-right bg-primary">
|
||||
<div class="card-body rounded-3 p-0 text-right bg-primary">
|
||||
<p class="card-text p-1 mb-0 text-white text-break text-wrap">${message_html}</p>
|
||||
<p class="text-right mb-0 p-1 text-white" style="text-align: right; font-size : 0.9rem">
|
||||
<span class="text-light" style="font-size: 0.7rem;">${timestamp} - </span>
|
||||
|
@ -675,6 +790,9 @@ update_chat = function(obj) {
|
|||
if (obj.percent >= 100){
|
||||
//document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-striped");
|
||||
document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-animated");
|
||||
document.getElementById('msg-' + obj._id + '-progress').classList.remove("bg-danger");
|
||||
document.getElementById('msg-' + obj._id + '-progress').classList.add("bg-primary");
|
||||
|
||||
document.getElementById('msg-' + obj._id + '-progress').innerHTML = '';
|
||||
} else {
|
||||
document.getElementById('msg-' + obj._id + '-progress').classList.add("progress-bar-striped");
|
||||
|
@ -734,28 +852,34 @@ update_chat = function(obj) {
|
|||
|
||||
//var file = atob(obj._attachments[filename]["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);
|
||||
|
||||
}).then(function(){
|
||||
|
||||
var file = blobUtil.arrayBufferToBinaryString(data)
|
||||
|
||||
// converting back to blob for debugging
|
||||
// length must be equal of file size
|
||||
var blob = blobUtil.binaryStringToBlob(file);
|
||||
console.log(blob)
|
||||
|
||||
|
||||
var data_with_attachment = doc.msg + split_char + filename + split_char + filetype + split_char + file;
|
||||
let Data = {
|
||||
command: "send_message",
|
||||
dxcallsign: doc.dxcallsign,
|
||||
mode: 255,
|
||||
frames: 1,
|
||||
data: data_with_attachment,
|
||||
checksum: doc.checksum,
|
||||
uuid: doc.uuid
|
||||
};
|
||||
console.log(Data)
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
console.log(binaryString)
|
||||
console.log(binaryString.length)
|
||||
|
||||
var data_with_attachment = doc.timestamp + split_char + utf8.encode(doc.msg) + split_char + filename + split_char + filetype + split_char + binaryString;
|
||||
let Data = {
|
||||
command: "send_message",
|
||||
dxcallsign: doc.dxcallsign,
|
||||
mode: 255,
|
||||
frames: 1,
|
||||
data: data_with_attachment,
|
||||
checksum: doc.checksum,
|
||||
uuid: doc.uuid
|
||||
};
|
||||
console.log(Data)
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
|
||||
});
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.log(err);
|
||||
|
@ -788,6 +912,7 @@ function saveFileToFolder(id) {
|
|||
console.log(data.length)
|
||||
//data = new Blob([data.buffer], { type: 'image/png' } /* (1) */)
|
||||
console.log(data)
|
||||
// we need to encode data because of error "an object could not be cloned"
|
||||
let Data = {
|
||||
file: data,
|
||||
filename: filename,
|
||||
|
@ -805,7 +930,7 @@ function saveFileToFolder(id) {
|
|||
}
|
||||
|
||||
|
||||
// function for setting an ICON to the correspinding state
|
||||
// function for setting an ICON to the corresponding state
|
||||
function get_icon_for_state(state) {
|
||||
if (state == 'transmit') {
|
||||
var status_icon = '<i class="bi bi-check" style="font-size:1rem;"></i>';
|
||||
|
@ -841,7 +966,7 @@ update_chat_obj_by_uuid = function(uuid) {
|
|||
add_obj_to_database = function(obj){
|
||||
db.put({
|
||||
_id: obj.uuid,
|
||||
timestamp: obj.timestamp,
|
||||
timestamp: parseInt(obj.timestamp),
|
||||
uuid: obj.uuid,
|
||||
dxcallsign: obj.dxcallsign,
|
||||
dxgrid: obj.dxgrid,
|
||||
|
@ -870,4 +995,37 @@ add_obj_to_database = function(obj){
|
|||
function scrollMessagesToBottom() {
|
||||
var messageBody = document.getElementById('message-container');
|
||||
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;
|
||||
};
|
|
@ -8,14 +8,55 @@ var configPath = path.join(configFolder, 'config.json')
|
|||
const config = require(configPath);
|
||||
|
||||
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// here we could add filter buttons, somewhen later..
|
||||
|
||||
document.getElementById('enable_filter_info').addEventListener('click', () => {
|
||||
if (document.getElementById('enable_filter_info').checked){
|
||||
display_class("table-info", true)
|
||||
} else {
|
||||
display_class("table-info", false)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('enable_filter_debug').addEventListener('click', () => {
|
||||
if (document.getElementById('enable_filter_debug').checked){
|
||||
display_class("table-debug", true)
|
||||
} else {
|
||||
display_class("table-debug", false)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('enable_filter_warning').addEventListener('click', () => {
|
||||
if (document.getElementById('enable_filter_warning').checked){
|
||||
display_class("table-warning", true)
|
||||
} else {
|
||||
display_class("table-warning", false)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('enable_filter_error').addEventListener('click', () => {
|
||||
if (document.getElementById('enable_filter_error').checked){
|
||||
display_class("table-danger", true)
|
||||
} else {
|
||||
display_class("table-danger", false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function display_class(class_name, state){
|
||||
var collection = document.getElementsByClassName(class_name);
|
||||
console.log(collection)
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
if (state == true){
|
||||
collection[i].style.display = "table-row";
|
||||
} else {
|
||||
collection[i].style.display = "None";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('action-update-log', (event, arg) => {
|
||||
|
||||
var entry = arg.entry
|
||||
|
@ -24,46 +65,124 @@ ipcRenderer.on('action-update-log', (event, arg) => {
|
|||
// https://stackoverflow.com/a/29497680
|
||||
entry = entry.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,'')
|
||||
|
||||
|
||||
|
||||
var tbl = document.getElementById("log");
|
||||
var row = document.createElement("tr");
|
||||
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement('span');
|
||||
datetime = new Date();
|
||||
timestampText.innerText = datetime.toISOString();
|
||||
|
||||
//datetime = new Date();
|
||||
//timestampText.innerText = datetime.toISOString();
|
||||
timestampText.innerText = entry.slice(0, 19);
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var type = document.createElement("td");
|
||||
var typeText = document.createElement('span');
|
||||
// typeText.innerText = entry.slice(10, 30).match(/[\[](.*)[^\]]/g);
|
||||
console.log(entry.match(/\[[^\]]+\]/g))
|
||||
|
||||
try{
|
||||
typeText.innerText = entry.match(/\[[^\]]+\]/g)[0];
|
||||
} catch(e){
|
||||
typeText.innerText = '-'
|
||||
}
|
||||
|
||||
|
||||
// let res = str.match(/[\[](.*)[^\]]/g);
|
||||
|
||||
type.appendChild(typeText);
|
||||
|
||||
var area = document.createElement("td");
|
||||
var areaText = document.createElement('span');
|
||||
//areaText.innerText = entry.slice(10, 50).match(/[\] \[](.*)[^\]]/g);
|
||||
//areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
|
||||
try{
|
||||
areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
} catch(e){
|
||||
areaText.innerText = '-'
|
||||
}
|
||||
area.appendChild(areaText);
|
||||
|
||||
var logEntry = document.createElement("td");
|
||||
var logEntryText = document.createElement('span');
|
||||
logEntryText.innerText = entry
|
||||
try{logEntryText.innerText = entry.split("]")[2];
|
||||
} catch(e){
|
||||
logEntryText.innerText = "-";
|
||||
}
|
||||
logEntry.appendChild(logEntryText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(type);
|
||||
row.appendChild(area);
|
||||
row.appendChild(logEntry);
|
||||
|
||||
//row.classList.add("table-blablubb");
|
||||
/*
|
||||
if (logEntryText.innerText.includes('ALSA lib pcm')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
*/
|
||||
if (typeText.innerText.includes('info')) {
|
||||
row.classList.add("table-info");
|
||||
}
|
||||
if (typeText.innerText.includes('debug')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes('warning')) {
|
||||
row.classList.add("table-warning");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes('error')) {
|
||||
row.classList.add("table-danger");
|
||||
}
|
||||
|
||||
|
||||
if (document.getElementById('enable_filter_info').checked) {
|
||||
row.style.display = "table-row"
|
||||
display_class("table-info", true)
|
||||
} else {
|
||||
row.style.display = "None"
|
||||
display_class("table-info", false)
|
||||
|
||||
}
|
||||
if (document.getElementById('enable_filter_debug').checked) {
|
||||
row.style.display = "table-row"
|
||||
display_class("table-secondary", true)
|
||||
} else {
|
||||
row.style.display = "None"
|
||||
display_class("table-secondary", false)
|
||||
|
||||
}
|
||||
if (document.getElementById('enable_filter_warning').checked) {
|
||||
row.style.display = "table-row"
|
||||
display_class("table-warning", true)
|
||||
} else {
|
||||
row.style.display = "None"
|
||||
display_class("table-warning", false)
|
||||
|
||||
}
|
||||
if (document.getElementById('enable_filter_error').checked) {
|
||||
row.style.display = "table-row"
|
||||
display_class("table-danger", true)
|
||||
} else {
|
||||
row.style.display = "None"
|
||||
display_class("table-danger", false)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
tbl.appendChild(row);
|
||||
|
||||
|
||||
|
||||
if (logEntryText.innerText.includes('ALSA lib pcm')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[info ]')) {
|
||||
row.classList.add("table-info");
|
||||
}
|
||||
if (logEntryText.innerText.includes('[debug ]')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[warning ]')) {
|
||||
row.classList.add("table-warning");
|
||||
}
|
||||
|
||||
if (logEntryText.innerText.includes('[error ]')) {
|
||||
row.classList.add("table-danger");
|
||||
}
|
||||
|
||||
|
||||
// scroll to bottom of page
|
||||
// https://stackoverflow.com/a/11715670
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const path = require('path');
|
||||
const {ipcRenderer} = require('electron');
|
||||
const {ipcRenderer, shell} = require('electron');
|
||||
const exec = require('child_process').spawn;
|
||||
const sock = require('./sock.js');
|
||||
const daemon = require('./daemon.js');
|
||||
|
@ -12,7 +12,7 @@ const {
|
|||
} = require('qth-locator');
|
||||
const os = require('os');
|
||||
|
||||
// split character used for appending addiotional data to files
|
||||
// split character used for appending additional data to files
|
||||
const split_char = '\0;';
|
||||
|
||||
|
||||
|
@ -22,6 +22,14 @@ var configFolder = path.join(appDataFolder, "FreeDATA");
|
|||
var configPath = path.join(configFolder, 'config.json');
|
||||
const config = require(configPath);
|
||||
|
||||
|
||||
// SET dbfs LEVEL GLOBAL
|
||||
// this is an attempt of reducing CPU LOAD
|
||||
// we are going to check if we have unequal values before we start calculating again
|
||||
var dbfs_level_raw = 0
|
||||
|
||||
|
||||
|
||||
// START INTERVALL COMMAND EXECUTION FOR STATES
|
||||
//setInterval(sock.getRxBuffer, 1000);
|
||||
|
||||
|
@ -29,6 +37,87 @@ const config = require(configPath);
|
|||
// WINDOW LISTENER
|
||||
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', () => {
|
||||
|
||||
|
@ -88,8 +177,8 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', ()
|
|||
// hamlib settings
|
||||
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_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
|
||||
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)
|
||||
|
||||
document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed;
|
||||
set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed)
|
||||
|
@ -160,7 +249,13 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
|||
document.getElementById("respondCQSwitch").checked = true;
|
||||
} else {
|
||||
document.getElementById("respondCQSwitch").checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(config.enable_explorer == 'True'){
|
||||
document.getElementById("ExplorerSwitch").checked = true;
|
||||
} else {
|
||||
document.getElementById("ExplorerSwitch").checked = false;
|
||||
}
|
||||
// theme selector
|
||||
|
||||
if(config.theme != 'default'){
|
||||
|
@ -194,16 +289,57 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
|||
if (config.spectrum == 'waterfall') {
|
||||
document.getElementById("waterfall-scatter-switch1").checked = true;
|
||||
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.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-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.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
|
||||
|
@ -277,10 +413,13 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
|||
// Create spectrum object on canvas with ID "waterfall"
|
||||
global.spectrum = new Spectrum(
|
||||
"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
|
||||
// disabled
|
||||
|
@ -649,7 +788,7 @@ document.getElementById('hamlib_rigctld_start').addEventListener('click', () =>
|
|||
|
||||
|
||||
|
||||
document.getElementById('hamlib_rigctld_command').value = paramList
|
||||
document.getElementById('hamlib_rigctld_command').value = paramList.join(" ") // join removes the commas
|
||||
|
||||
console.log(paramList)
|
||||
console.log(rigctldPath)
|
||||
|
@ -676,21 +815,55 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
// on click waterfall scatter toggle view
|
||||
// waterfall
|
||||
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("waterfall").style.display = 'block';
|
||||
document.getElementById("waterfall").style.visibility = 'visible';
|
||||
document.getElementById("waterfall").style.height = '100%';
|
||||
|
||||
config.spectrum = 'waterfall';
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
});
|
||||
// scatter
|
||||
document.getElementById("waterfall-scatter-switch2").addEventListener("click", () => {
|
||||
document.getElementById("scatter").style.display = 'block';
|
||||
document.getElementById("scatter").style.visibility = 'visible';
|
||||
document.getElementById("scatter").style.height = '100%';
|
||||
|
||||
document.getElementById("waterfall").style.visibility = 'hidden';
|
||||
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';
|
||||
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
|
||||
|
@ -709,13 +882,7 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
});
|
||||
|
||||
// on change port and host
|
||||
document.getElementById("tnc_adress").addEventListener("change", () => {
|
||||
console.log(document.getElementById("tnc_adress").value);
|
||||
config.tnc_host = document.getElementById("tnc_adress").value;
|
||||
config.daemon_host = document.getElementById("tnc_adress").value;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
});
|
||||
|
||||
|
||||
// on change ping callsign
|
||||
document.getElementById("dxCall").addEventListener("change", () => {
|
||||
|
@ -727,13 +894,45 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
|
||||
});
|
||||
|
||||
|
||||
// on change port and host
|
||||
document.getElementById("tnc_adress").addEventListener("change", () => {
|
||||
console.log(document.getElementById("tnc_adress").value);
|
||||
config.tnc_host = document.getElementById("tnc_adress").value;
|
||||
config.daemon_host = document.getElementById("tnc_adress").value;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
let Data = {
|
||||
port: document.getElementById("tnc_port").value,
|
||||
adress: document.getElementById("tnc_adress").value,
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-ip', Data);
|
||||
|
||||
Data = {
|
||||
port: parseInt(document.getElementById("tnc_port").value) + 1,
|
||||
adress: document.getElementById("tnc_adress").value,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-ip', Data);
|
||||
});
|
||||
|
||||
// on change tnc port
|
||||
document.getElementById("tnc_port").addEventListener("change", () => {
|
||||
|
||||
config.tnc_port = document.getElementById("tnc_port").value;
|
||||
config.daemon_port = parseInt(document.getElementById("tnc_port").value) + 1;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
let Data = {
|
||||
port: document.getElementById("tnc_port").value,
|
||||
adress: document.getElementById("tnc_adress").value,
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-ip', Data);
|
||||
|
||||
Data = {
|
||||
port: parseInt(document.getElementById("tnc_port").value) + 1,
|
||||
adress: document.getElementById("tnc_adress").value,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-ip', Data);
|
||||
|
||||
});
|
||||
|
||||
// on change audio TX Level
|
||||
|
@ -758,22 +957,21 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
ipcRenderer.send('run-tnc-command', Data);
|
||||
});
|
||||
// saveMyCall button clicked
|
||||
document.getElementById("saveMyCall").addEventListener("click", () => {
|
||||
document.getElementById("myCall").addEventListener("input", () => {
|
||||
callsign = document.getElementById("myCall").value;
|
||||
ssid = document.getElementById("myCallSSID").value;
|
||||
callsign_ssid = callsign.toUpperCase() + '-' + ssid;
|
||||
config.mycall = callsign_ssid;
|
||||
|
||||
// split document title by looking for Call then split and update it
|
||||
var documentTitle = document.title.split('Call:')
|
||||
document.title = documentTitle[0] + 'Call: ' + callsign_ssid;
|
||||
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
daemon.saveMyCall(callsign_ssid);
|
||||
});
|
||||
|
||||
// saveMyGrid button clicked
|
||||
document.getElementById("saveMyGrid").addEventListener("click", () => {
|
||||
document.getElementById("myGrid").addEventListener("input", () => {
|
||||
grid = document.getElementById("myGrid").value;
|
||||
config.mygrid = grid;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
@ -872,7 +1070,16 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
}
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
});
|
||||
|
||||
|
||||
// enable explorer Switch clicked
|
||||
document.getElementById("ExplorerSwitch").addEventListener("click", () => {
|
||||
if(document.getElementById("ExplorerSwitch").checked == true){
|
||||
config.enable_explorer = "True";
|
||||
} else {
|
||||
config.enable_explorer = "False";
|
||||
}
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
});
|
||||
|
||||
// enable fsk Switch clicked
|
||||
document.getElementById("fskModeSwitch").addEventListener("click", () => {
|
||||
|
@ -916,9 +1123,17 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
config.theme = theme;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
});
|
||||
|
||||
// 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", () => {
|
||||
config.update_channel = document.getElementById("update_channel_selector").value;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
@ -947,6 +1162,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
sock.stopBeacon();
|
||||
});
|
||||
|
||||
// Explorer button clicked
|
||||
document.getElementById("openExplorer").addEventListener("click", () => {
|
||||
shell.openExternal('https://explorer.freedata.app/?myCall=' + document.getElementById("myCall").value);
|
||||
});
|
||||
|
||||
// startTNC button clicked
|
||||
document.getElementById("startTNC").addEventListener("click", () => {
|
||||
|
||||
|
@ -1008,6 +1228,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
var respond_to_cq = "False";
|
||||
}
|
||||
|
||||
if (document.getElementById("ExplorerSwitch").checked == true){
|
||||
var enable_explorer = "True";
|
||||
} else {
|
||||
var enable_explorer = "False";
|
||||
}
|
||||
|
||||
// loop through audio device list and select
|
||||
for(i = 0; i < document.getElementById("audio_input_selectbox").length; i++) {
|
||||
|
@ -1067,6 +1292,7 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
config.tx_audio_level = tx_audio_level;
|
||||
config.respond_to_cq = respond_to_cq;
|
||||
config.rx_buffer_size = rx_buffer_size;
|
||||
config.enable_explorer = enable_explorer;
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
|
@ -1085,7 +1311,7 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
|
|||
*/
|
||||
|
||||
|
||||
daemon.startTNC(callsign_ssid, mygrid, rx_audio, tx_audio, radiocontrol, deviceid, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port, enable_fft, enable_scatter, low_bandwidth_mode, tuning_range_fmin, tuning_range_fmax, enable_fsk, tx_audio_level, respond_to_cq, rx_buffer_size);
|
||||
daemon.startTNC(callsign_ssid, mygrid, rx_audio, tx_audio, radiocontrol, deviceid, deviceport, pttprotocol, pttport, serialspeed, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port, enable_fft, enable_scatter, low_bandwidth_mode, tuning_range_fmin, tuning_range_fmax, enable_fsk, tx_audio_level, respond_to_cq, rx_buffer_size, enable_explorer);
|
||||
|
||||
|
||||
})
|
||||
|
@ -1259,28 +1485,24 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
|
||||
}
|
||||
|
||||
|
||||
// TOE TIME OF EXECUTION --> How many time needs a command to be executed until data arrives
|
||||
// deactivated this feature, beacuse its useless at this time. maybe it is getting more interesting, if we are working via network
|
||||
// but for this we need to find a nice place for this on the screen
|
||||
/*
|
||||
if (typeof(arg.toe) == 'undefined') {
|
||||
var toe = 0
|
||||
} else {
|
||||
var toe = arg.toe
|
||||
if (typeof(arg.mycallsign) !== 'undefined') {
|
||||
// split document title by looking for Call then split and update it
|
||||
var documentTitle = document.title.split('Call:')
|
||||
document.title = documentTitle[0] + 'Call: ' + arg.mycallsign;
|
||||
}
|
||||
document.getElementById("toe").innerHTML = toe + ' ms'
|
||||
*/
|
||||
|
||||
|
||||
// update mygrid information with data from tnc
|
||||
if (typeof(arg.mygrid) !== 'undefined') {
|
||||
document.getElementById("myGrid").value = arg.mygrid;
|
||||
}
|
||||
|
||||
// DATA STATE
|
||||
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: {
|
||||
legend: {
|
||||
display: false,
|
||||
|
@ -1334,16 +1556,12 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var data = arg.scatter
|
||||
var newdata = {
|
||||
var scatterData = arg.scatter
|
||||
var newScatterData = {
|
||||
datasets: [{
|
||||
//label: 'constellation diagram',
|
||||
data: data,
|
||||
options: config,
|
||||
data: scatterData,
|
||||
options: scatterConfig,
|
||||
backgroundColor: 'rgb(255, 99, 132)'
|
||||
}],
|
||||
};
|
||||
|
@ -1353,25 +1571,122 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
} else {
|
||||
var scatterSize = arg.scatter.length;
|
||||
}
|
||||
if (global.data != newdata && scatterSize > 0) {
|
||||
try {
|
||||
global.myChart.destroy();
|
||||
} catch (e) {
|
||||
// myChart not yet created
|
||||
console.log(e);
|
||||
|
||||
if (global.scatterData != newScatterData && scatterSize > 0) {
|
||||
global.scatterData = newScatterData;
|
||||
|
||||
if (typeof(global.scatterChart) == 'undefined') {
|
||||
var scatterCtx = document.getElementById('scatter').getContext('2d');
|
||||
global.scatterChart = new Chart(scatterCtx, {
|
||||
type: 'scatter',
|
||||
data: global.scatterData,
|
||||
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 } },
|
||||
}
|
||||
}
|
||||
|
||||
global.data = newdata;
|
||||
|
||||
|
||||
var ctx = document.getElementById('scatter').getContext('2d');
|
||||
global.myChart = new Chart(ctx, {
|
||||
type: 'scatter',
|
||||
data: global.data,
|
||||
options: config
|
||||
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
|
||||
if (arg.ptt_state == 'True') {
|
||||
document.getElementById("ptt_state").className = "btn btn-sm btn-danger";
|
||||
|
@ -1381,6 +1696,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
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
|
||||
if (arg.channel_busy == 'True') {
|
||||
document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
|
||||
|
@ -1438,6 +1769,14 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
|
||||
}
|
||||
|
||||
// HAMLIB STATUS
|
||||
if (arg.hamlib_status == 'connected') {
|
||||
document.getElementById("rigctld_state").className = "btn btn-success btn-sm";
|
||||
|
||||
} else {
|
||||
document.getElementById("rigctld_state").className = "btn btn-secondary btn-sm";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// BEACON STATE
|
||||
|
@ -1457,15 +1796,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
document.getElementById("stopBeacon").disabled = true;
|
||||
document.getElementById("beaconInterval").disabled = false;
|
||||
}
|
||||
// RMS
|
||||
/*
|
||||
var rms_level = Math.round((arg.rms_level/60) * 100)
|
||||
document.getElementById("rms_level").setAttribute("aria-valuenow", rms_level);
|
||||
document.getElementById("rms_level").setAttribute("style", "width:" + rms_level + "%;");
|
||||
*/
|
||||
// dbfs
|
||||
// https://www.moellerstudios.org/converting-amplitude-representations/
|
||||
if (dbfs_level_raw != arg.dbfs_level){
|
||||
dbfs_level_raw = arg.dbfs_level
|
||||
dbfs_level = Math.pow(10, arg.dbfs_level / 20) * 100
|
||||
|
||||
document.getElementById("dbfs_level_value").innerHTML = Math.round(arg.dbfs_level) + ' dBFS'
|
||||
document.getElementById("dbfs_level").setAttribute("aria-valuenow", dbfs_level);
|
||||
document.getElementById("dbfs_level").setAttribute("style", "width:" + dbfs_level + "%;");
|
||||
}
|
||||
|
||||
// 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
|
||||
document.getElementById("mode").innerHTML = arg.mode;
|
||||
|
@ -1488,7 +1834,30 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
var arq_bytes_per_minute_compressed = Math.round(arg.arq_bytes_per_minute * arg.arq_compression_factor);
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -1507,6 +1876,7 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
if(arg.speed_level >= 4) {
|
||||
document.getElementById("speed_level").className = "bi bi-reception-4";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2003,10 +2373,20 @@ ipcRenderer.on('run-tnc-command', (event, arg) => {
|
|||
if (arg.command == 'set_tx_audio_level') {
|
||||
sock.setTxAudioLevel(arg.tx_audio_level);
|
||||
}
|
||||
if (arg.command == 'record_audio') {
|
||||
sock.record_audio();
|
||||
}
|
||||
if (arg.command == 'send_test_frame') {
|
||||
sock.sendTestFrame();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (arg.command == 'frequency') {
|
||||
sock.set_frequency(arg.frequency);
|
||||
}
|
||||
|
||||
if (arg.command == 'mode') {
|
||||
sock.set_mode(arg.mode);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
@ -2152,6 +2532,14 @@ ipcRenderer.on('action-show-arq-toast-datachannel-opening', (event, data) => {
|
|||
toast.show();
|
||||
});
|
||||
|
||||
// DATA CHANNEL WAITING TOAST
|
||||
ipcRenderer.on('action-show-arq-toast-datachannel-waiting', (event, data) => {
|
||||
var toastDATACHANNELwaiting = document.getElementById('toastDATACHANNELwaiting');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastDATACHANNELwaiting); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
||||
|
||||
// DATA CHANNEL OPEN TOAST
|
||||
ipcRenderer.on('action-show-arq-toast-datachannel-open', (event, data) => {
|
||||
var toastDATACHANNELopen = document.getElementById('toastDATACHANNELopen');
|
||||
|
@ -2204,10 +2592,43 @@ ipcRenderer.on('action-show-arq-toast-transmission-transmitted', (event, data) =
|
|||
// ARQ TRANSMISSION TRANSMITTING
|
||||
ipcRenderer.on('action-show-arq-toast-transmission-transmitting', (event, data) => {
|
||||
|
||||
document.getElementById("transmission_progress").className = "progress-bar progress-bar-striped progress-bar-animated bg-primary";
|
||||
var toastARQtransmitting = document.getElementById('toastARQtransmitting');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmitting); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
//document.getElementById("toastARQtransmittingSNR").className = "progress-bar progress-bar-striped progress-bar-animated bg-primary";
|
||||
var toastARQtransmittingSNR = document.getElementById('toastARQtransmittingSNR');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmittingSNR); // Returns a Bootstrap toast instance
|
||||
|
||||
var irs_snr = data["data"][0].irs_snr;
|
||||
|
||||
if(irs_snr <= 0){
|
||||
document.getElementById("toastARQtransmittingSNR").className = "toast align-items-center text-white bg-danger border-0";
|
||||
document.getElementById('toastARQtransmittingSNRValue').innerHTML = " low " + irs_snr;
|
||||
toast.show();
|
||||
|
||||
} else if(irs_snr > 0 && irs_snr <= 5){
|
||||
document.getElementById("toastARQtransmittingSNR").className = "toast align-items-center text-white bg-warning border-0";
|
||||
document.getElementById('toastARQtransmittingSNRValue').innerHTML = " okay " + irs_snr;
|
||||
toast.show();
|
||||
|
||||
} else if(irs_snr > 5 && irs_snr < 12.7){
|
||||
document.getElementById("toastARQtransmittingSNR").className = "toast align-items-center text-white bg-success border-0";
|
||||
document.getElementById('toastARQtransmittingSNRValue').innerHTML = " good " + irs_snr;
|
||||
toast.show();
|
||||
|
||||
} else if(irs_snr >= 12.7){
|
||||
document.getElementById("toastARQtransmittingSNR").className = "toast align-items-center text-white bg-success border-0";
|
||||
document.getElementById('toastARQtransmittingSNRValue').innerHTML = " really good 12.7+";
|
||||
toast.show();
|
||||
|
||||
} else {
|
||||
console.log("no snr info available")
|
||||
document.getElementById("transmission_progress").className = "progress-bar progress-bar-striped progress-bar-animated bg-primary";
|
||||
var toastARQtransmitting = document.getElementById('toastARQtransmitting');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmitting); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// ARQ TRANSMISSION RECEIVED
|
||||
|
@ -2223,15 +2644,15 @@ ipcRenderer.on('action-show-arq-toast-transmission-received', (event, data) => {
|
|||
ipcRenderer.on('action-show-arq-toast-transmission-receiving', (event, data) => {
|
||||
|
||||
document.getElementById("transmission_progress").className = "progress-bar progress-bar-striped progress-bar-animated bg-primary";
|
||||
var toastARQreceiving = document.getElementById('toastARQreceiving');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQreceiving); // Returns a Bootstrap toast instance
|
||||
var toastARQsessionreceiving = document.getElementById('toastARQreceiving');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionreceiving); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
||||
// ARQ SESSION CONNECTING
|
||||
ipcRenderer.on('action-show-arq-toast-session-connecting', (event, data) => {
|
||||
|
||||
var toastARQreceiving = document.getElementById('toastARQsessionconnecting');
|
||||
var toastARQsessionconnecting = document.getElementById('toastARQsessionconnecting');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionconnecting); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
@ -2239,15 +2660,24 @@ ipcRenderer.on('action-show-arq-toast-session-connecting', (event, data) => {
|
|||
// ARQ SESSION CONNECTED
|
||||
ipcRenderer.on('action-show-arq-toast-session-connected', (event, data) => {
|
||||
|
||||
var toastARQreceiving = document.getElementById('toastARQsessionconnected');
|
||||
var toastARQsessionconnected = document.getElementById('toastARQsessionconnected');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionconnected); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
||||
// ARQ SESSION CONNECTED
|
||||
ipcRenderer.on('action-show-arq-toast-session-waiting', (event, data) => {
|
||||
|
||||
var toastARQsessionwaiting = document.getElementById('toastARQsessionwaiting');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionwaiting); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
||||
|
||||
// ARQ SESSION CLOSE
|
||||
ipcRenderer.on('action-show-arq-toast-session-close', (event, data) => {
|
||||
|
||||
var toastARQreceiving = document.getElementById('toastARQsessionclose');
|
||||
var toastARQsessionclose = document.getElementById('toastARQsessionclose');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionclose); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
@ -2255,7 +2685,7 @@ ipcRenderer.on('action-show-arq-toast-session-close', (event, data) => {
|
|||
// ARQ SESSION FAILED
|
||||
ipcRenderer.on('action-show-arq-toast-session-failed', (event, data) => {
|
||||
|
||||
var toastARQreceiving = document.getElementById('toastARQsessionfailed');
|
||||
var toastARQsessionfailed = document.getElementById('toastARQsessionfailed');
|
||||
var toast = bootstrap.Toast.getOrCreateInstance(toastARQsessionfailed); // Returns a Bootstrap toast instance
|
||||
toast.show();
|
||||
});
|
||||
|
@ -2286,9 +2716,17 @@ function set_setting_switch(setting_switch, enable_object, state){
|
|||
|
||||
setInterval(checkRigctld, 500)
|
||||
function checkRigctld(){
|
||||
ipcRenderer.send('request-check-rigctld');
|
||||
|
||||
var rigctld_ip = document.getElementById("hamlib_rigctld_ip").value;
|
||||
var rigctld_port = document.getElementById("hamlib_rigctld_port").value;
|
||||
|
||||
let Data = {
|
||||
ip: rigctld_ip,
|
||||
port: rigctld_port
|
||||
};
|
||||
ipcRenderer.send('request-check-rigctld', Data);
|
||||
}
|
||||
|
||||
ipcRenderer.on('action-check-rigctld', (event, data) => {
|
||||
console.log(data)
|
||||
document.getElementById("hamlib_rigctld_status").value = data["state"];
|
||||
});
|
||||
});
|
||||
|
|
104
gui/sock.js
104
gui/sock.js
|
@ -19,7 +19,7 @@ var client = new net.Socket();
|
|||
var socketchunk = ''; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
const split_char = '\0;\1;'
|
||||
|
||||
// globals for getting new data only if available so we are saving bandwidth
|
||||
var rxBufferLengthTnc = 0
|
||||
|
@ -30,6 +30,10 @@ var rxMsgBufferLengthGui = 0
|
|||
// global to keep track of TNC connection error emissions
|
||||
var tncShowConnectStateError = 1
|
||||
|
||||
// global for storing ip information
|
||||
var tnc_port = config.tnc_port;
|
||||
var tnc_host = config.tnc_host;
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectTNC, 2000)
|
||||
|
||||
|
@ -37,13 +41,13 @@ function connectTNC() {
|
|||
//exports.connectTNC = function(){
|
||||
//socketLog.info('connecting to TNC...')
|
||||
|
||||
//clear message buffer after reconnecting or inital connection
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = '';
|
||||
|
||||
if (config.tnclocation == 'localhost') {
|
||||
client.connect(3000, '127.0.0.1')
|
||||
} else {
|
||||
client.connect(config.tnc_port, config.tnc_host)
|
||||
client.connect(tnc_port, tnc_host)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +60,7 @@ client.on('connect', function(data) {
|
|||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
rms_level: 0
|
||||
dbfs_level: 0
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
|
||||
|
@ -85,7 +89,7 @@ client.on('error', function(data) {
|
|||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
rms_level: 0
|
||||
dbfs_level: 0
|
||||
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
|
@ -191,6 +195,8 @@ client.on('data', function(socketdata) {
|
|||
rxMsgBufferLengthTnc = data['rx_msg_buffer_length']
|
||||
|
||||
let Data = {
|
||||
mycallsign: data['mycallsign'],
|
||||
mygrid: data['mygrid'],
|
||||
ptt_state: data['ptt_state'],
|
||||
busy_state: data['tnc_state'],
|
||||
arq_state: data['arq_state'],
|
||||
|
@ -200,7 +206,7 @@ client.on('data', function(socketdata) {
|
|||
speed_level: data['speed_level'],
|
||||
mode: data['mode'],
|
||||
bandwidth: data['bandwidth'],
|
||||
rms_level: data['audio_rms'],
|
||||
dbfs_level: data['audio_dbfs'],
|
||||
fft: data['fft'],
|
||||
channel_busy: data['channel_busy'],
|
||||
scatter: data['scatter'],
|
||||
|
@ -216,11 +222,17 @@ client.on('data', function(socketdata) {
|
|||
arq_rx_n_current_arq_frame: data['arq_rx_n_current_arq_frame'],
|
||||
arq_n_arq_frames_per_data_frame: data['arq_n_arq_frames_per_data_frame'],
|
||||
arq_bytes_per_minute: data['arq_bytes_per_minute'],
|
||||
arq_seconds_until_finish: data['arq_seconds_until_finish'],
|
||||
arq_compression_factor: data['arq_compression_factor'],
|
||||
total_bytes: data['total_bytes'],
|
||||
arq_transmission_percent: data['arq_transmission_percent'],
|
||||
stations: data['stations'],
|
||||
beacon_state: data['beacon_state'],
|
||||
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);
|
||||
|
@ -305,6 +317,10 @@ client.on('data', function(socketdata) {
|
|||
} else if (data['status'] == 'connected') {
|
||||
ipcRenderer.send('request-show-arq-toast-session-connected', {data: [data]});
|
||||
|
||||
// ARQ OPENING
|
||||
} else if (data['status'] == 'waiting') {
|
||||
ipcRenderer.send('request-show-arq-toast-session-waiting', {data: [data]});
|
||||
|
||||
// ARQ OPENING
|
||||
} else if (data['status'] == 'close') {
|
||||
ipcRenderer.send('request-show-arq-toast-session-close', {data: [data]});
|
||||
|
@ -326,6 +342,10 @@ client.on('data', function(socketdata) {
|
|||
} else if (data['status'] == 'opening') {
|
||||
ipcRenderer.send('request-show-arq-toast-datachannel-opening', {data: [data]});
|
||||
|
||||
// ARQ WAITING
|
||||
} else if (data['status'] == 'waiting') {
|
||||
ipcRenderer.send('request-show-arq-toast-datachannel-waiting', {data: [data]});
|
||||
|
||||
|
||||
// ARQ TRANSMISSION FAILED
|
||||
} else if (data['status'] == 'failed') {
|
||||
|
@ -501,22 +521,27 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data,
|
|||
|
||||
// Send Message
|
||||
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
|
||||
data = utf8.encode(data)
|
||||
socketLog.info(data)
|
||||
//data = utf8.encode(data)
|
||||
|
||||
//socketLog.info(data)
|
||||
|
||||
|
||||
var datatype = "m"
|
||||
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
||||
socketLog.info(data)
|
||||
|
||||
|
||||
|
||||
socketLog.info(btoa(data))
|
||||
//socketLog.info(data)
|
||||
console.log(data)
|
||||
|
||||
console.log("CHECKSUM" + checksum)
|
||||
//socketLog.info(btoa(data))
|
||||
data = btoa(data)
|
||||
|
||||
|
||||
//command = '{"type" : "arq", "command" : "send_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}'
|
||||
command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '"}]}'
|
||||
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("-------------------------------------")
|
||||
writeTncCommand(command)
|
||||
|
@ -554,7 +579,7 @@ exports.stopBeacon = function() {
|
|||
|
||||
// OPEN ARQ SESSION
|
||||
exports.connectARQ = function(dxcallsign) {
|
||||
command = '{"type" : "arq", "command" : "connect", "dxcallsign": "'+ dxcallsign + '"}'
|
||||
command = '{"type" : "arq", "command" : "connect", "dxcallsign": "'+ dxcallsign + '", "attempts": "15"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
|
@ -564,8 +589,53 @@ exports.disconnectARQ = function() {
|
|||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
// SEND SINE
|
||||
// SEND TEST FRAME
|
||||
exports.sendTestFrame = function() {
|
||||
command = '{"type" : "set", "command" : "send_test_frame"}'
|
||||
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) => {
|
||||
client.destroy();
|
||||
let Data = {
|
||||
busy_state: "-",
|
||||
arq_state: "-",
|
||||
//channel_state: "-",
|
||||
frequency: "-",
|
||||
mode: "-",
|
||||
bandwidth: "-",
|
||||
dbfs_level: 0
|
||||
};
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
tnc_port = arg.port;
|
||||
tnc_host = arg.adress;
|
||||
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};
|
||||
|
|
|
@ -54,11 +54,15 @@
|
|||
</div>
|
||||
<hr class="m-0">
|
||||
<! ------messages area ---------------------------------------------------------------------->
|
||||
<div class="container overflow-auto" id="message-container" style="height: calc(100% - 110px);">
|
||||
<div class="container overflow-auto" id="message-container" style="height: calc(100% - 150px);">
|
||||
<div class="tab-content" id="nav-tabContent"> </div>
|
||||
<!--<div class="container position-absolute bottom-0">--></div>
|
||||
<!-- </div>-->
|
||||
<div class="container-fluid mt-2 p-0">
|
||||
|
||||
<input type="checkbox" id="expand_textarea" class="btn-check" autocomplete="off">
|
||||
<label class="btn d-flex justify-content-center" id="expand_textarea_label" for="expand_textarea"><i id="expand_textarea_button" class="bi bi-chevron-compact-up"></i></label>
|
||||
|
||||
<div class="input-group bottom-0 w-100">
|
||||
<!--<input class="form-control" maxlength="8" style="max-width: 6rem; text-transform:uppercase; display:none" id="chatModuleDxCall" placeholder="DX CALL"></input>-->
|
||||
<!--<button class="btn btn-sm btn-primary me-2" id="emojipickerbutton" type="button">--><i id="emojipickerbutton" class="bi bi-emoji-smile m-1" style="font-size: 1.5rem; color: grey;"></i><!--</button>-->
|
||||
|
|
|
@ -44,7 +44,10 @@
|
|||
<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 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-2" data-bs-toggle="offcanvas" data-bs-target="#receivedFilesSidebar" id="openReceivedFiles" type="button" > <strong>Files </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
<i class="bi bi-file-earmark-arrow-down-fill" style="font-size: 1rem; color: white;"></i>
|
||||
|
@ -52,7 +55,8 @@
|
|||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Send files through HF. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openDataModule" data-bs-toggle="offcanvas" data-bs-target="#transmitFileSidebar" type="button" style="display: None;"> <strong>TX File </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
</button>
|
||||
-->
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="true" title="Settings and Info">
|
||||
<button type="button" id="infoModalButton" data-bs-toggle="modal" data-bs-target="#infoModal" class="btn btn-sm btn-secondary"><strong>Settings </strong>
|
||||
<i class="bi bi-sliders" style="font-size: 1rem; color: white;"></i>
|
||||
|
@ -136,6 +140,15 @@
|
|||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DATACHANNEL WAITING -->
|
||||
<div class="toast align-items-center text-white bg-warning border-0" id="toastDATACHANNELwaiting" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">DATACHANNEL BUSY! Waiting...</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STOPPING TRANSMISSION -->
|
||||
<div class="toast align-items-center text-white bg-danger border-0" id="toastTRANSMISSIONstopped" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
|
@ -181,10 +194,19 @@
|
|||
<!-- ARQ TRANSMITTING -->
|
||||
<div class="toast align-items-center text-white bg-secondary border-0" id="toastARQtransmitting" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">TRANSMITTING FILE...</div>
|
||||
<div class="toast-body">TRANSMITTING...</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ARQ SNR INFORMATION -->
|
||||
<div class="toast align-items-center text-white bg-secondary border-0" id="toastARQtransmittingSNR" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">TRANSMITTING - SNR IRS:<span id="toastARQtransmittingSNRValue"></span></div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ARQ TRANSMITTING FAILED -->
|
||||
<div class="toast align-items-center text-white bg-danger border-0" id="toastARQtransmittingfailed" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
|
@ -213,6 +235,13 @@
|
|||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ARQ SESSION WAITING-->
|
||||
<div class="toast align-items-center text-white bg-warning border-0" id="toastARQsessionwaiting" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">CHANNEL BUSY - Waiting...</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ARQ SESSION CLOSE-->
|
||||
<div class="toast align-items-center text-white bg-success border-0" id="toastARQsessionclose" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
|
@ -703,7 +732,6 @@
|
|||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-success" id="saveMyCall" type="button"> <i class="bi bi-check2" style="font-size: 1rem; color: white;"></i> </button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
|
@ -711,7 +739,6 @@
|
|||
<i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control mr-1" style="max-width: 6rem" placeholder="locator" id="myGrid" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
|
||||
<button class="btn btn-sm btn-success" id="saveMyGrid" type="button"> <i class="bi bi-check2" style="font-size: 1rem; color: white;"></i> </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -742,16 +769,19 @@
|
|||
<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>
|
||||
<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 class="card-body p-2">
|
||||
<div class="progress mb-0" style="height: 15px;">
|
||||
<div class="progress-bar progress-bar-striped bg-primary" id="rms_level" 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">RX AUDIO LEVEL - not implemented yet</p>
|
||||
<div class="progress-bar progress-bar-striped bg-primary" id="dbfs_level" 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" id="dbfs_level_value">dBFS</p>
|
||||
</div>
|
||||
<div class="progress mb-0" style="height: 5px;">
|
||||
<div class="progress-bar progress-bar-striped bg-warning" role="progressbar" style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: 80%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar progress-bar-striped bg-warning" role="progressbar" style="width: 1%" aria-valuenow="1" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: 89%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar progress-bar-striped bg-warning" role="progressbar" style="width: 20%" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: 29%" aria-valuenow="29" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -808,37 +838,19 @@
|
|||
<div class="card-header p-1">
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
<button class="btn btn-sm btn-secondary" id="channel_busy" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>">busy</button>
|
||||
</div>
|
||||
<div class="card-body p-1" style="height: 200px">
|
||||
<!-- 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-->
|
||||
<canvas id="waterfall" style="position: relative; z-index: 2;"></canvas>
|
||||
<canvas id="scatter" style="position: relative; z-index: 1;"></canvas>
|
||||
<canvas id="waterfall" style="position: relative; z-index: 2; transform: translateZ(0);"></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>
|
||||
|
@ -1037,24 +1049,64 @@
|
|||
<nav class="navbar fixed-bottom navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
<!--<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>
|
||||
<!--<span class="input-group-text" id="basic-addon1"><strong>BW</strong></span>--><span class="input-group-text" id="bandwidth">---</span> </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="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 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">
|
||||
|
@ -1067,6 +1119,7 @@
|
|||
<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>
|
||||
<!--<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>
|
||||
|
@ -1074,8 +1127,8 @@
|
|||
<!-- bootstrap -->
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- chart.js -->
|
||||
<script src="../node_modules/chart.js/dist/chart.min.js"></script>
|
||||
<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>
|
||||
<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="../ui.js"></script>-->
|
||||
<!-- WATERFALL -->
|
||||
<script src="waterfall/colormap.js"></script>
|
||||
|
@ -1139,6 +1192,17 @@
|
|||
<option value="zephyr">Zephyr</option>
|
||||
</select>
|
||||
</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>
|
||||
<select class="form-select form-select-sm w-50" id="update_channel_selector">
|
||||
<option value="latest">stable</option>
|
||||
|
@ -1196,7 +1260,7 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable 563Hz mode</label>
|
||||
<label class="input-group-text w-50">Enable 563Hz only mode</label>
|
||||
<label class="input-group-text bg-white w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="500HzModeSwitch">
|
||||
|
@ -1204,6 +1268,15 @@
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable Explorer Publishing <br> (https://explorer.freedata.app) </label>
|
||||
<label class="input-group-text bg-white w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="ExplorerSwitch">
|
||||
<label class="form-check-label" for="ExplorerSwitch">Publish</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Respond to CQ</label>
|
||||
<label class="input-group-text bg-white w-50">
|
||||
|
@ -1253,4 +1326,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
|
||||
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>FreeDATA - Live Log</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -17,10 +20,32 @@
|
|||
<!-- chart.js -->
|
||||
|
||||
|
||||
<nav class="navbar fixed-top bg-light">
|
||||
<div class="container-fluid">
|
||||
<input type="checkbox" class="btn-check" id="enable_filter_info" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-info" for="enable_filter_info">info</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="enable_filter_debug" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="enable_filter_debug">debug</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="enable_filter_warning" autocomplete="off">
|
||||
<label class="btn btn-outline-warning" for="enable_filter_warning">warning</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="enable_filter_error" autocomplete="off">
|
||||
<label class="btn btn-outline-danger" for="enable_filter_error">error</label>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="tableFixHead">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Timestamp</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Area</th>
|
||||
<th scope="col">Log entry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -35,6 +60,7 @@
|
|||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
11
gui/src/splash.html
Normal file
11
gui/src/splash.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||
</head>
|
||||
<body>
|
||||
<img src="img/icon_cube_border.png" width="100%" height="100%">
|
||||
</body>
|
||||
</html>
|
|
@ -8,6 +8,7 @@ body {
|
|||
/*Progress bars with centered text*/
|
||||
.progress {
|
||||
position: relative;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.progress span {
|
||||
|
@ -27,4 +28,41 @@ html {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#chatModuleMessage {
|
||||
resize: none;
|
||||
border-radius:15px;
|
||||
}
|
||||
|
||||
#expand_textarea_label{
|
||||
border: 0;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
/* fixed border table header */
|
||||
.tableFixHead {
|
||||
overflow: auto;
|
||||
height: 90vh;
|
||||
}
|
||||
.tableFixHead thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
th {
|
||||
background:#eee;
|
||||
}
|
||||
|
||||
|
||||
/* ------ emoji picker customization --------- */
|
||||
.picker {
|
||||
border-radius: 10px:
|
||||
|
||||
}
|
|
@ -68,14 +68,55 @@ Spectrum.prototype.drawFFT = function(bins) {
|
|||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
Spectrum.prototype.drawSpectrum = function(bins) {
|
||||
//Spectrum.prototype.drawSpectrum = function(bins) {
|
||||
Spectrum.prototype.drawSpectrum = function() {
|
||||
var width = this.ctx.canvas.width;
|
||||
var height = this.ctx.canvas.height;
|
||||
|
||||
|
||||
// Modification by DJ2LS
|
||||
// Draw bandwidth lines
|
||||
// TODO: Math not correct. But a first attempt
|
||||
// it seems position is more or less equal to frequenzy by factor 10
|
||||
// eg. position 150 == 1500Hz
|
||||
/*
|
||||
// CENTER LINE
|
||||
this.ctx_wf.beginPath();
|
||||
this.ctx_wf.moveTo(150,0);
|
||||
this.ctx_wf.lineTo(150, height);
|
||||
this.ctx_wf.lineWidth = 1;
|
||||
this.ctx_wf.strokeStyle = '#8C8C8C';
|
||||
this.ctx_wf.stroke()
|
||||
*/
|
||||
|
||||
// 586Hz and 1700Hz LINES
|
||||
var linePositionLow = 121.6; //150 - bandwith/20
|
||||
var linePositionHigh = 178.4; //150 + bandwidth/20
|
||||
var linePositionLow2 = 65; //150 - bandwith/20
|
||||
var linePositionHigh2 = 235; //150 + bandwith/20
|
||||
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.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.strokeStyle = '#C3C3C3';
|
||||
this.ctx_wf.stroke()
|
||||
|
||||
// ---- END OF MODIFICATION ------
|
||||
|
||||
|
||||
// Fill with black
|
||||
this.ctx.fillStyle = "white";
|
||||
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
|
||||
if (this.averaging > 0) {
|
||||
if (!this.binsAverage || this.binsAverage.length != bins.length) {
|
||||
|
@ -128,6 +169,12 @@ Spectrum.prototype.drawSpectrum = function(bins) {
|
|||
|
||||
// Copy axes from offscreen canvas
|
||||
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() {
|
||||
|
@ -196,7 +243,8 @@ Spectrum.prototype.addData = function(data) {
|
|||
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
|
||||
this.imagedata = this.ctx_wf.createImageData(data.length, 1);
|
||||
}
|
||||
this.drawSpectrum(data);
|
||||
//this.drawSpectrum(data);
|
||||
this.drawSpectrum();
|
||||
this.addWaterfallRow(data);
|
||||
this.resize();
|
||||
}
|
||||
|
|
|
@ -6,15 +6,21 @@ pyserial
|
|||
sounddevice
|
||||
structlog
|
||||
ujson
|
||||
requests
|
||||
chardet
|
||||
colorama
|
||||
ordered-set
|
||||
nuitka
|
||||
pyinstaller
|
||||
|
||||
# Development and test dependencies
|
||||
autopep8
|
||||
black
|
||||
isort
|
||||
pycodestyle
|
||||
pyinstaller
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-cover
|
||||
pytest-coverage
|
||||
pytest-rerunfailures
|
||||
pick
|
|
@ -133,7 +133,7 @@ def analyze_results(station1: list, station2: list, call_list: list):
|
|||
@pytest.mark.parametrize("freedv_mode", ["datac1", "datac3"])
|
||||
@pytest.mark.parametrize("n_frames_per_burst", [1]) # Higher fpb is broken.
|
||||
@pytest.mark.parametrize("message_no", range(len(messages)))
|
||||
@pytest.mark.flaky(reruns=2)
|
||||
@pytest.mark.flaky(reruns=3)
|
||||
def test_chat_text(
|
||||
freedv_mode: str, n_frames_per_burst: int, message_no: int, tmp_path
|
||||
):
|
||||
|
|
|
@ -15,6 +15,7 @@ Uses util_datac0.py in separate process to perform the data transfer.
|
|||
"""
|
||||
|
||||
import multiprocessing
|
||||
import numpy as np
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
@ -62,16 +63,23 @@ def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
|
|||
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
||||
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
||||
|
||||
# frame = bytearray(14)
|
||||
# frame[:1] = bytes([frame_type])
|
||||
# frame[1:4] = dxcallsign_crc
|
||||
# frame[4:7] = mycallsign_crc
|
||||
# frame[7:13] = mycallsign_bytes
|
||||
session_id = np.random.bytes(1)
|
||||
frame = bytearray(14)
|
||||
frame[:1] = bytes([frame_type])
|
||||
frame[1:4] = dxcallsign_crc
|
||||
frame[4:7] = mycallsign_crc
|
||||
frame[7:13] = mycallsign_bytes
|
||||
frame[1:2] = session_id
|
||||
frame[2:5] = dxcallsign_crc
|
||||
frame[5:8] = mycallsign_crc
|
||||
frame[8:14] = mycallsign_bytes
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
|
||||
def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
|
||||
"""
|
||||
Generate the session_close frame.
|
||||
|
||||
|
@ -85,6 +93,29 @@ def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
|
|||
return t_create_frame(223, mycall, dxcall)
|
||||
|
||||
|
||||
def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray:
|
||||
"""
|
||||
Generate the session_close frame.
|
||||
|
||||
:param session_id: Session to close
|
||||
:type mycall: int
|
||||
:return: Bytearray of the requested frame
|
||||
: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)
|
||||
frame = bytearray(14)
|
||||
frame[:1] = bytes([223])
|
||||
frame[1:2] = session_id
|
||||
frame[2:5] = dxcallsign_crc
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
|
||||
"""
|
||||
Generate the create_session frame.
|
||||
|
@ -150,18 +181,24 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
|
|||
assert static.ARQ_SESSION_STATE == "connecting"
|
||||
|
||||
# Set up a frame from a non-associated station.
|
||||
foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
||||
foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
|
||||
# foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
||||
# foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
|
||||
|
||||
close_frame = t_create_session_close("ZZ0ZZ-0", "ZZ0ZZ-0")
|
||||
# close_frame = t_create_session_close_old("ZZ0ZZ-0", "ZZ0ZZ-0")
|
||||
open_session = create_frame[1:2]
|
||||
wrong_session = np.random.bytes(1)
|
||||
while wrong_session == open_session:
|
||||
wrong_session = np.random.bytes(1)
|
||||
close_frame = t_create_session_close(wrong_session, dxcall)
|
||||
print_frame(close_frame)
|
||||
assert (
|
||||
helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False
|
||||
), f"{helpers.get_crc_24(static.DXCALLSIGN)} == {bytes(close_frame[4:7])} but should be not equal."
|
||||
|
||||
assert (
|
||||
helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
|
||||
), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal."
|
||||
# assert (
|
||||
# helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False
|
||||
# ), f"{helpers.get_crc_24(static.DXCALLSIGN)} == {bytes(close_frame[4:7])} but should be not equal."
|
||||
|
||||
# assert (
|
||||
# helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
|
||||
# ), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal."
|
||||
|
||||
# Send the non-associated session close frame to the TNC
|
||||
tnc.received_session_close(close_frame)
|
||||
|
@ -221,7 +258,12 @@ def t_valid_disconnect(mycall: str, dxcall: str):
|
|||
assert static.ARQ_SESSION_STATE == "connecting"
|
||||
|
||||
# Create packet to be 'received' by this station.
|
||||
close_frame = t_create_session_close(mycall=dxcall, dxcall=mycall)
|
||||
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
|
||||
open_session = create_frame[1:2]
|
||||
print(dxcall)
|
||||
print("#####################################################")
|
||||
close_frame = t_create_session_close(open_session, mycall)
|
||||
print(close_frame[2:5])
|
||||
print_frame(close_frame)
|
||||
tnc.received_session_close(close_frame)
|
||||
|
||||
|
@ -241,7 +283,7 @@ def t_valid_disconnect(mycall: str, dxcall: str):
|
|||
|
||||
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
|
||||
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
|
||||
@pytest.mark.flaky(reruns=2)
|
||||
# @pytest.mark.flaky(reruns=2)
|
||||
def test_foreign_disconnect(mycall: str, dxcall: str):
|
||||
proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall))
|
||||
# print("Starting threads.")
|
||||
|
|
|
@ -44,6 +44,8 @@ def t_setup(
|
|||
static.MYGRID = bytes("AA12aa", "utf-8")
|
||||
static.RESPOND_TO_CQ = True
|
||||
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
# override ARQ SESSION STATE for allowing disconnect command
|
||||
static.ARQ_SESSION_STATE = "connected"
|
||||
|
||||
mycallsign = helpers.callsign_to_bytes(mycall)
|
||||
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||
|
@ -187,6 +189,8 @@ def t_highsnr_arq_short_station1(
|
|||
data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall}
|
||||
sock.process_tnc_commands(json.dumps(data, indent=None))
|
||||
time.sleep(0.5)
|
||||
# override ARQ SESSION STATE for allowing disconnect command
|
||||
static.ARQ_SESSION_STATE = "connected"
|
||||
sock.process_tnc_commands(json.dumps(data, indent=None))
|
||||
|
||||
# Allow enough time for this side to process the disconnect frame.
|
||||
|
|
|
@ -41,6 +41,8 @@ def t_setup(
|
|||
static.MYGRID = bytes("AA12aa", "utf-8")
|
||||
static.RESPOND_TO_CQ = True
|
||||
static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
# override ARQ SESSION STATE for allowing disconnect command
|
||||
static.ARQ_SESSION_STATE = "connected"
|
||||
|
||||
mycallsign = helpers.callsign_to_bytes(mycall)
|
||||
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||
|
|
|
@ -164,7 +164,8 @@ def t_datac0_1(
|
|||
break
|
||||
time.sleep(0.1)
|
||||
log.info("station1, first")
|
||||
|
||||
# override ARQ SESSION STATE for allowing disconnect command
|
||||
static.ARQ_SESSION_STATE = "connected"
|
||||
data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall}
|
||||
sock.process_tnc_commands(json.dumps(data, indent=None))
|
||||
time.sleep(0.5)
|
||||
|
@ -295,6 +296,6 @@ def t_datac0_2(
|
|||
assert item in str(
|
||||
sock.SOCKET_QUEUE.queue
|
||||
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||
|
||||
assert '"arq":"session","status":"close"' 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)
|
||||
log.warning("station2: Exiting!")
|
||||
|
|
|
@ -170,7 +170,8 @@ def t_datac0_1(
|
|||
log.debug("STOP test, resetting DX callsign")
|
||||
static.DXCALLSIGN = orig_dxcall
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
||||
|
||||
# override ARQ SESSION STATE for allowing disconnect command
|
||||
static.ARQ_SESSION_STATE = "connected"
|
||||
data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall}
|
||||
sock.process_tnc_commands(json.dumps(data, indent=None))
|
||||
time.sleep(0.5)
|
||||
|
@ -286,7 +287,10 @@ def t_datac0_2(
|
|||
|
||||
# Allow enough time for this side to receive the disconnect frame.
|
||||
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:
|
||||
log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue))
|
||||
break
|
||||
|
@ -301,6 +305,6 @@ def t_datac0_2(
|
|||
assert item not in str(
|
||||
sock.SOCKET_QUEUE.queue
|
||||
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||
|
||||
assert '"arq":"session","status":"close"' 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)
|
||||
log.warning("station2: Exiting!")
|
||||
|
|
12
tnc/audio.py
12
tnc/audio.py
|
@ -31,7 +31,7 @@ def get_audio_devices():
|
|||
sd._terminate()
|
||||
sd._initialize()
|
||||
|
||||
log.debug("[AUD] get_audio_devices")
|
||||
# log.debug("[AUD] get_audio_devices")
|
||||
with multiprocessing.Manager() as manager:
|
||||
proxy_input_devices = manager.list()
|
||||
proxy_output_devices = manager.list()
|
||||
|
@ -42,8 +42,9 @@ def get_audio_devices():
|
|||
proc.start()
|
||||
proc.join()
|
||||
|
||||
#log.debug("[AUD] get_audio_devices: input_devices:", list=f"{proxy_input_devices}")
|
||||
#log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}")
|
||||
# additional logging for audio devices
|
||||
# log.debug("[AUD] get_audio_devices: input_devices:", list=f"{proxy_input_devices}")
|
||||
# log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}")
|
||||
return list(proxy_input_devices), list(proxy_output_devices)
|
||||
|
||||
|
||||
|
@ -52,7 +53,10 @@ def device_crc(device) -> str:
|
|||
crc_hwid = crc_algorithm(bytes(f"{device}", encoding="utf-8"))
|
||||
crc_hwid = crc_hwid.to_bytes(2, byteorder="big")
|
||||
crc_hwid = crc_hwid.hex()
|
||||
return f"{device['name']} [{crc_hwid}]"
|
||||
|
||||
hostapi_name = sd.query_hostapis(device['hostapi'])['name']
|
||||
|
||||
return f"{device['name']} [{hostapi_name}] [{crc_hwid}]"
|
||||
|
||||
|
||||
def fetch_audio_devices(input_devices, output_devices):
|
||||
|
|
|
@ -23,7 +23,8 @@ class FREEDV_MODE(Enum):
|
|||
"""
|
||||
Enumeration for codec2 modes and names
|
||||
"""
|
||||
|
||||
sig0 = 14
|
||||
sig1 = 14
|
||||
datac0 = 14
|
||||
datac1 = 10
|
||||
datac3 = 12
|
||||
|
@ -103,6 +104,9 @@ api.freedv_open_advanced.restype = ctypes.c_void_p
|
|||
api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore
|
||||
api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int
|
||||
|
||||
api.freedv_get_modem_extended_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
api.freedv_get_modem_extended_stats.restype = ctypes.c_int
|
||||
|
||||
api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore
|
||||
api.freedv_nin.restype = ctypes.c_int
|
||||
|
||||
|
@ -208,8 +212,8 @@ api.FREEDV_MODE_FSK_LDPC_1_ADV.tone_spacing = 200
|
|||
api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = "H_256_512_4".encode("utf-8") # code word
|
||||
|
||||
# ------- MODEM STATS STRUCTURES
|
||||
MODEM_STATS_NC_MAX = 50 + 1
|
||||
MODEM_STATS_NR_MAX = 160
|
||||
MODEM_STATS_NC_MAX = 50 + 1 * 2
|
||||
MODEM_STATS_NR_MAX = 160 * 2
|
||||
MODEM_STATS_ET_MAX = 8
|
||||
MODEM_STATS_EYE_IND_MAX = 160
|
||||
MODEM_STATS_NSPEC = 512
|
||||
|
@ -233,10 +237,12 @@ class MODEMSTATS(ctypes.Structure):
|
|||
("pre", ctypes.c_int),
|
||||
("post", ctypes.c_int),
|
||||
("uw_fails", ctypes.c_int),
|
||||
("rx_eye", (ctypes.c_float * MODEM_STATS_ET_MAX) * MODEM_STATS_EYE_IND_MAX),
|
||||
("neyetr", ctypes.c_int), # How many eye traces are plotted
|
||||
("neyesamp", ctypes.c_int), # How many samples in the eye diagram
|
||||
("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)),
|
||||
("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)),
|
||||
("fft_cfg", ctypes.c_void_p)
|
||||
]
|
||||
|
||||
|
||||
|
|
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,13 +7,18 @@ class CONFIG:
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, configfile: str):
|
||||
# set up logger
|
||||
self.log = structlog.get_logger("CONFIG")
|
||||
|
||||
# init configparser
|
||||
self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
||||
self.config_name = "config.ini"
|
||||
|
||||
try:
|
||||
self.config_name = configfile
|
||||
|
||||
except Exception:
|
||||
self.config_name = "config.ini"
|
||||
|
||||
self.log.info("[CFG] logfile init", file=self.config_name)
|
||||
|
||||
|
@ -45,7 +50,8 @@ class CONFIG:
|
|||
|
||||
self.config['STATION'] = {'#Station settings': None,
|
||||
'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,
|
||||
|
@ -56,14 +62,15 @@ class CONFIG:
|
|||
}
|
||||
self.config['RADIO'] = {'#Radio settings': None,
|
||||
'radiocontrol': data[13],
|
||||
'devicename': data[5],
|
||||
'deviceport': data[6],
|
||||
'serialspeed': data[7],
|
||||
'pttprotocol': data[8],
|
||||
'pttport': data[9],
|
||||
'data_bits': data[10],
|
||||
'stop_bits': data[11],
|
||||
'handshake': data[12],
|
||||
# TODO: disabled because we dont need these settings anymore
|
||||
#'devicename': data[5],
|
||||
#'deviceport': data[6],
|
||||
#'serialspeed': data[7],
|
||||
#'pttprotocol': data[8],
|
||||
#'pttport': data[9],
|
||||
#'data_bits': data[10],
|
||||
#'stop_bits': data[11],
|
||||
#'handshake': data[12],
|
||||
'rigctld_ip': data[14],
|
||||
'rigctld_port': data[15]
|
||||
}
|
||||
|
@ -74,7 +81,8 @@ class CONFIG:
|
|||
'fmin': data[19],
|
||||
'fmax': data[20],
|
||||
'qrv': data[23],
|
||||
'rxbuffersize': data[24]
|
||||
'rxbuffersize': data[24],
|
||||
'explorer': data[25]
|
||||
}
|
||||
try:
|
||||
with open(self.config_name, 'w') as configfile:
|
||||
|
|
|
@ -93,7 +93,7 @@ class DAEMON:
|
|||
"[DMN] update_audio_devices: Exception gathering audio devices:",
|
||||
e=err1,
|
||||
)
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
|
||||
def update_serial_devices(self):
|
||||
"""
|
||||
|
@ -114,7 +114,7 @@ class DAEMON:
|
|||
)
|
||||
|
||||
static.SERIAL_DEVICES = serial_devices
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
except Exception as err1:
|
||||
self.log.error(
|
||||
"[DMN] update_serial_devices: Exception gathering serial devices:",
|
||||
|
@ -156,7 +156,8 @@ class DAEMON:
|
|||
# data[22] tx-audio-level
|
||||
# data[23] respond_to_cq
|
||||
# data[24] rx_buffer_size
|
||||
|
||||
# data[25] explorer
|
||||
# data[26] ssid_list
|
||||
|
||||
if data[0] == "STARTTNC":
|
||||
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
|
||||
|
@ -248,6 +249,17 @@ class DAEMON:
|
|||
options.append("--rx-buffer-size")
|
||||
options.append(data[24])
|
||||
|
||||
if data[25] == "True":
|
||||
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
|
||||
config.write_entire_config(data)
|
||||
|
||||
|
@ -317,9 +329,11 @@ class DAEMON:
|
|||
|
||||
# check how we want to control the radio
|
||||
if radiocontrol == "direct":
|
||||
import rig
|
||||
print("direct hamlib support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif radiocontrol == "rigctl":
|
||||
import rigctl as rig
|
||||
print("rigctl support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif radiocontrol == "rigctld":
|
||||
import rigctld as rig
|
||||
else:
|
||||
|
@ -402,7 +416,7 @@ if __name__ == "__main__":
|
|||
mainlog.error("[DMN] logger init error", exception=err)
|
||||
|
||||
# init config
|
||||
config = config.CONFIG()
|
||||
config = config.CONFIG("config.ini")
|
||||
|
||||
try:
|
||||
mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT)
|
||||
|
@ -429,4 +443,4 @@ if __name__ == "__main__":
|
|||
version=static.VERSION,
|
||||
)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
|
|
1390
tnc/data_handler.py
1390
tnc/data_handler.py
File diff suppressed because it is too large
Load diff
68
tnc/explorer.py
Normal file
68
tnc/explorer.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
Created on 05.11.23
|
||||
|
||||
@author: DJ2LS
|
||||
"""
|
||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||
|
||||
import requests
|
||||
import threading
|
||||
import time
|
||||
import ujson as json
|
||||
import structlog
|
||||
import static
|
||||
|
||||
log = structlog.get_logger("explorer")
|
||||
|
||||
|
||||
class explorer():
|
||||
def __init__(self):
|
||||
self.explorer_url = "https://explorer.freedata.app/api.php"
|
||||
self.publish_interval = 120
|
||||
|
||||
self.interval_thread = threading.Thread(target=self.interval, name="interval", daemon=True)
|
||||
self.interval_thread.start()
|
||||
|
||||
def interval(self):
|
||||
while True:
|
||||
self.push()
|
||||
threading.Event().wait(self.publish_interval)
|
||||
|
||||
def push(self):
|
||||
|
||||
frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY
|
||||
band = "USB"
|
||||
callsign = str(static.MYCALLSIGN, "utf-8")
|
||||
gridsquare = str(static.MYGRID, "utf-8")
|
||||
version = str(static.VERSION)
|
||||
bandwidth = str(static.LOW_BANDWIDTH_MODE)
|
||||
beacon = str(static.BEACON_STATE)
|
||||
|
||||
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, '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)
|
||||
try:
|
||||
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
||||
# print(response.status_code)
|
||||
# print(response.content)
|
||||
|
||||
except Exception as e:
|
||||
log.warning("[EXPLORER] connection lost")
|
|
@ -7,7 +7,7 @@ block_cipher = None
|
|||
daemon_a = Analysis(['daemon.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[( './lib/hamlib/linux/python3.8/site-packages/libhamlib.so.4', '.' )],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
@ -25,6 +25,7 @@ daemon_exe = EXE(daemon_pyz,
|
|||
[],
|
||||
exclude_binaries=True,
|
||||
name='freedata-daemon',
|
||||
bundle_identifier='com.dj2ls.freedata-daemon',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
@ -62,6 +63,7 @@ tnc_exe = EXE(tnc_pyz,
|
|||
[],
|
||||
exclude_binaries=True,
|
||||
name='freedata-tnc',
|
||||
bundle_identifier='com.dj2ls.freedata-tnc',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
|
|
@ -5,10 +5,12 @@ Created on Fri Dec 25 21:25:14 2020
|
|||
@author: DJ2LS
|
||||
"""
|
||||
import time
|
||||
|
||||
from datetime import datetime,timezone
|
||||
import crcengine
|
||||
import static
|
||||
import structlog
|
||||
import numpy as np
|
||||
import threading
|
||||
|
||||
log = structlog.get_logger("helpers")
|
||||
|
||||
|
@ -24,7 +26,7 @@ def wait(seconds: float) -> bool:
|
|||
timeout = time.time() + seconds
|
||||
|
||||
while time.time() < timeout:
|
||||
time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -130,7 +132,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
|
|||
# check if buffer empty
|
||||
if len(static.HEARD_STATIONS) == 0:
|
||||
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
|
||||
else:
|
||||
|
@ -314,7 +316,25 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
|
|||
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid)
|
||||
return [True, bytes(call_with_ssid)]
|
||||
|
||||
return [False, ""]
|
||||
return [False, b'']
|
||||
|
||||
|
||||
def check_session_id(id: bytes, id_to_check: bytes):
|
||||
"""
|
||||
Funktion to check if we received the correct session id
|
||||
|
||||
Args:
|
||||
id: our own session id
|
||||
id_to_check: The session id byte we want to check
|
||||
|
||||
Returns:
|
||||
True
|
||||
False
|
||||
"""
|
||||
if id_to_check == b'\x00':
|
||||
return False
|
||||
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
||||
return id == id_to_check
|
||||
|
||||
|
||||
def encode_grid(grid):
|
||||
|
@ -374,11 +394,7 @@ def decode_grid(b_code_word: bytes):
|
|||
|
||||
int_val = int(code_word & 0b111111111)
|
||||
int_first, int_sec = divmod(int_val, 18)
|
||||
# int_first = int_val // 18
|
||||
# int_sec = int_val % 18
|
||||
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||
|
||||
return grid
|
||||
return chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||
|
||||
|
||||
def encode_call(call):
|
||||
|
@ -428,3 +444,21 @@ def decode_call(b_code_word: bytes):
|
|||
call = call[:-1] + ssid # remove the last char from call and replace with SSID
|
||||
|
||||
return call
|
||||
|
||||
|
||||
def snr_to_bytes(snr):
|
||||
"""create a byte from snr value """
|
||||
# make sure we have onl 1 byte snr
|
||||
# min max = -12.7 / 12.7
|
||||
# enough for detecting if a channel is good or bad
|
||||
snr = snr * 10
|
||||
snr = np.clip(snr, -127, 127)
|
||||
snr = int(snr).to_bytes(1, byteorder='big', signed=True)
|
||||
return snr
|
||||
|
||||
|
||||
def snr_from_bytes(snr):
|
||||
"""create int from snr byte"""
|
||||
snr = int.from_bytes(snr, byteorder='big', signed=True)
|
||||
snr = snr / 10
|
||||
return snr
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -1,3 +0,0 @@
|
|||
# Hamlib Python binding
|
||||
|
||||
we need to copy "libhamlib.so.4.0.4" from build_dir/lib/ to this path, rename it to "libhamlib.so.4 and add it to source directory of tnc and daemon binary
|
Binary file not shown.
|
@ -1,41 +0,0 @@
|
|||
# _Hamlib.la - a libtool library file
|
||||
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-9
|
||||
#
|
||||
# Please DO NOT delete this file!
|
||||
# It is necessary for linking the library.
|
||||
|
||||
# The name that we can dlopen(3).
|
||||
dlname='_Hamlib.so'
|
||||
|
||||
# Names of this library.
|
||||
library_names='_Hamlib.so _Hamlib.so _Hamlib.so'
|
||||
|
||||
# The name of the static archive.
|
||||
old_library='_Hamlib.a'
|
||||
|
||||
# Linker flags that cannot go in dependency_libs.
|
||||
inherited_linker_flags=' -pthread'
|
||||
|
||||
# Libraries that this one depends upon.
|
||||
dependency_libs=' /home/dj2ls/hamlib4.4/lib/libhamlib.la -ldl -lm -lusb-1.0 -L/usr/lib -lpython3.8'
|
||||
|
||||
# Names of additional weak libraries provided by this library
|
||||
weak_library_names=''
|
||||
|
||||
# Version information for _Hamlib.
|
||||
current=0
|
||||
age=0
|
||||
revision=0
|
||||
|
||||
# Is this an already installed library?
|
||||
installed=yes
|
||||
|
||||
# Should we warn about portability when linking against -modules?
|
||||
shouldnotlink=yes
|
||||
|
||||
# Files to dlopen/dlpreopen
|
||||
dlopen=''
|
||||
dlpreopen=''
|
||||
|
||||
# Directory that this library needs to be installed in:
|
||||
libdir='/home/dj2ls/hamlib4.4/lib/python3.8/site-packages'
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
# hamlib win32 4.4
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
# hamlib win64 4.4
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl
Normal file
BIN
tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl
Normal file
Binary file not shown.
BIN
tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl
Normal file
BIN
tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl
Normal file
Binary file not shown.
|
@ -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 = [
|
||||
# Add the log level and a timestamp to the event_dict if the log entry
|
||||
# is not from structlog.
|
||||
|
|
222
tnc/main.py
222
tnc/main.py
|
@ -7,6 +7,14 @@ Created on Tue Dec 22 16:58:45 2020
|
|||
|
||||
main module for running the tnc
|
||||
"""
|
||||
|
||||
|
||||
# run tnc self test on startup before we are doing other things
|
||||
# import selftest
|
||||
# selftest.TEST()
|
||||
|
||||
# continue if we passed the test
|
||||
|
||||
import argparse
|
||||
import multiprocessing
|
||||
import os
|
||||
|
@ -22,10 +30,11 @@ import log_handler
|
|||
import modem
|
||||
import static
|
||||
import structlog
|
||||
import explorer
|
||||
import json
|
||||
|
||||
log = structlog.get_logger("main")
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""
|
||||
a signal handler, which closes the network/socket when closing the application
|
||||
|
@ -49,12 +58,28 @@ if __name__ == "__main__":
|
|||
# --------------------------------------------GET PARAMETER INPUTS
|
||||
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(
|
||||
"--use-config",
|
||||
dest="configfile",
|
||||
action="store_true",
|
||||
default=False,
|
||||
type=str,
|
||||
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(
|
||||
"--mycall",
|
||||
dest="mycall",
|
||||
|
@ -68,7 +93,7 @@ if __name__ == "__main__":
|
|||
nargs="*",
|
||||
default=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
help="SSID list we are responding to",
|
||||
type=str,
|
||||
type=int,
|
||||
)
|
||||
PARSER.add_argument(
|
||||
"--mygrid",
|
||||
|
@ -82,14 +107,14 @@ if __name__ == "__main__":
|
|||
dest="audio_input_device",
|
||||
default=0,
|
||||
help="listening sound card",
|
||||
type=int,
|
||||
type=str,
|
||||
)
|
||||
PARSER.add_argument(
|
||||
"--tx",
|
||||
dest="audio_output_device",
|
||||
default=0,
|
||||
help="transmitting sound card",
|
||||
type=int,
|
||||
type=str,
|
||||
)
|
||||
PARSER.add_argument(
|
||||
"--port",
|
||||
|
@ -246,81 +271,131 @@ if __name__ == "__main__":
|
|||
help="Set the maximum size of rx buffer.",
|
||||
type=int,
|
||||
)
|
||||
|
||||
PARSER.add_argument(
|
||||
"--explorer",
|
||||
dest="enable_explorer",
|
||||
action="store_true",
|
||||
help="Enable sending tnc data to https://explorer.freedata.app",
|
||||
)
|
||||
ARGS = PARSER.parse_args()
|
||||
if ARGS.configfile:
|
||||
# init config
|
||||
config = config.CONFIG().read_config()
|
||||
|
||||
# set save to folder state for allowing downloading files to local file system
|
||||
static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder
|
||||
|
||||
if not ARGS.configfile:
|
||||
|
||||
|
||||
# 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)
|
||||
try:
|
||||
mycallsign = bytes(ARGS.mycall.upper(), "utf-8")
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||
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.SSID_LIST = [] ####
|
||||
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'])
|
||||
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.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
|
||||
try:
|
||||
static.AUDIO_OUTPUT_DEVICE = int(ARGS.audio_output_device)
|
||||
except ValueError:
|
||||
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
||||
|
||||
static.PORT = ARGS.socket_port
|
||||
static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name
|
||||
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
|
||||
static.HAMLIB_PTT_TYPE = ARGS.hamlib_ptt_type
|
||||
static.HAMLIB_PTT_PORT = ARGS.hamlib_ptt_port
|
||||
static.HAMLIB_SERIAL_SPEED = str(ARGS.hamlib_serialspeed)
|
||||
static.HAMLIB_DATA_BITS = str(ARGS.hamlib_data_bits)
|
||||
static.HAMLIB_STOP_BITS = str(ARGS.hamlib_stop_bits)
|
||||
static.HAMLIB_HANDSHAKE = ARGS.hamlib_handshake
|
||||
static.HAMLIB_RADIOCONTROL = ARGS.hamlib_radiocontrol
|
||||
static.HAMLIB_RIGCTLD_IP = ARGS.rigctld_ip
|
||||
static.HAMLIB_RIGCTLD_PORT = str(ARGS.rigctld_port)
|
||||
static.ENABLE_SCATTER = ARGS.send_scatter
|
||||
static.ENABLE_FFT = ARGS.send_fft
|
||||
static.ENABLE_FSK = ARGS.enable_fsk
|
||||
static.LOW_BANDWIDTH_MODE = ARGS.low_bandwidth_mode
|
||||
static.TUNING_RANGE_FMIN = ARGS.tuning_range_fmin
|
||||
static.TUNING_RANGE_FMAX = ARGS.tuning_range_fmax
|
||||
static.TX_AUDIO_LEVEL = ARGS.tx_audio_level
|
||||
static.RESPOND_TO_CQ = ARGS.enable_respond_to_cq
|
||||
static.RX_BUFFER_SIZE = ARGS.rx_buffer_size
|
||||
static.ENABLE_EXPLORER = ARGS.enable_explorer
|
||||
except Exception as e:
|
||||
log.error("[DMN] Error reading config file", exception=e)
|
||||
|
||||
else:
|
||||
# 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(ARGS.mycall.upper(), "utf-8")
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||
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)
|
||||
|
||||
static.SSID_LIST = ARGS.ssid_list
|
||||
static.MYGRID = bytes(ARGS.mygrid, "utf-8")
|
||||
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
|
||||
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
||||
static.PORT = ARGS.socket_port
|
||||
static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name
|
||||
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
|
||||
static.HAMLIB_PTT_TYPE = ARGS.hamlib_ptt_type
|
||||
static.HAMLIB_PTT_PORT = ARGS.hamlib_ptt_port
|
||||
static.HAMLIB_SERIAL_SPEED = str(ARGS.hamlib_serialspeed)
|
||||
static.HAMLIB_DATA_BITS = str(ARGS.hamlib_data_bits)
|
||||
static.HAMLIB_STOP_BITS = str(ARGS.hamlib_stop_bits)
|
||||
static.HAMLIB_HANDSHAKE = ARGS.hamlib_handshake
|
||||
static.HAMLIB_RADIOCONTROL = ARGS.hamlib_radiocontrol
|
||||
static.HAMLIB_RIGCTLD_IP = ARGS.rigctld_ip
|
||||
static.HAMLIB_RIGCTLD_PORT = str(ARGS.rigctld_port)
|
||||
static.ENABLE_SCATTER = ARGS.send_scatter
|
||||
static.ENABLE_FFT = ARGS.send_fft
|
||||
static.ENABLE_FSK = ARGS.enable_fsk
|
||||
static.LOW_BANDWIDTH_MODE = ARGS.low_bandwidth_mode
|
||||
static.TUNING_RANGE_FMIN = ARGS.tuning_range_fmin
|
||||
static.TUNING_RANGE_FMAX = ARGS.tuning_range_fmax
|
||||
static.TX_AUDIO_LEVEL = ARGS.tx_audio_level
|
||||
static.RESPOND_TO_CQ = ARGS.enable_respond_to_cq
|
||||
static.RX_BUFFER_SIZE = ARGS.rx_buffer_size
|
||||
#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
|
||||
import sock
|
||||
|
@ -358,6 +433,11 @@ if __name__ == "__main__":
|
|||
# start modem
|
||||
modem = modem.RF()
|
||||
|
||||
# optionally start explorer module
|
||||
if static.ENABLE_EXPLORER:
|
||||
log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=static.ENABLE_EXPLORER)
|
||||
explorer = explorer.explorer()
|
||||
|
||||
# --------------------------------------------START CMD SERVER
|
||||
try:
|
||||
log.info("[TNC] Starting TCP/IP socket", port=static.PORT)
|
||||
|
@ -375,4 +455,4 @@ if __name__ == "__main__":
|
|||
log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err)
|
||||
sys.exit(1)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
|
|
687
tnc/modem.py
687
tnc/modem.py
|
@ -5,6 +5,7 @@ Created on Wed Dec 23 07:04:24 2020
|
|||
|
||||
@author: DJ2LS
|
||||
"""
|
||||
|
||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
|
@ -15,15 +16,16 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
import wave
|
||||
import codec2
|
||||
import itertools
|
||||
import numpy as np
|
||||
import sock
|
||||
import sounddevice as sd
|
||||
import static
|
||||
import structlog
|
||||
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
|
||||
RXCHANNEL = ""
|
||||
|
@ -32,10 +34,19 @@ TXCHANNEL = ""
|
|||
static.TRANSMITTING = False
|
||||
|
||||
# Receive only specific modes to reduce CPU load
|
||||
RECEIVE_SIG0 = True
|
||||
RECEIVE_SIG1 = False
|
||||
RECEIVE_DATAC1 = False
|
||||
RECEIVE_DATAC3 = False
|
||||
RECEIVE_FSK_LDPC_1 = False
|
||||
|
||||
# state buffer
|
||||
SIG0_DATAC0_STATE = []
|
||||
SIG1_DATAC0_STATE = []
|
||||
DAT0_DATAC1_STATE = []
|
||||
DAT0_DATAC3_STATE = []
|
||||
|
||||
|
||||
|
||||
class RF:
|
||||
"""Class to encapsulate interactions between the audio device and codec2"""
|
||||
|
@ -58,6 +69,7 @@ class RF:
|
|||
self.AUDIO_CHANNELS = 1
|
||||
self.MODE = 0
|
||||
|
||||
|
||||
# Locking state for mod out so buffer will be filled before we can use it
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/127
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
||||
|
@ -81,111 +93,68 @@ class RF:
|
|||
self.fft_data = bytes()
|
||||
|
||||
# Open codec2 instances
|
||||
# Datac0 - control frames
|
||||
self.datac0_freedv = ctypes.cast(
|
||||
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
|
||||
)
|
||||
self.c_lib.freedv_set_tuning_range(
|
||||
self.datac0_freedv,
|
||||
ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||
ctypes.c_float(static.TUNING_RANGE_FMAX),
|
||||
)
|
||||
self.datac0_bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
|
||||
)
|
||||
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv, 1)
|
||||
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
|
||||
# Additional Datac0-specific information - these are not referenced anywhere else.
|
||||
# self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2
|
||||
# self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(
|
||||
# self.datac0_freedv
|
||||
# )
|
||||
# self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(
|
||||
# self.datac0_freedv
|
||||
# )
|
||||
# self.datac0_n_tx_preamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv)
|
||||
# )
|
||||
# self.datac0_n_tx_postamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv)
|
||||
# )
|
||||
# DATAC0
|
||||
# SIGNALLING MODE 0 - Used for Connecting - Payload 14 Bytes
|
||||
self.sig0_datac0_freedv, \
|
||||
self.sig0_datac0_bytes_per_frame, \
|
||||
self.sig0_datac0_bytes_out, \
|
||||
self.sig0_datac0_buffer, \
|
||||
self.sig0_datac0_nin = \
|
||||
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None)
|
||||
|
||||
# Datac1 - higher-bandwidth data frames
|
||||
self.datac1_freedv = ctypes.cast(
|
||||
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
|
||||
)
|
||||
self.c_lib.freedv_set_tuning_range(
|
||||
self.datac1_freedv,
|
||||
ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||
ctypes.c_float(static.TUNING_RANGE_FMAX),
|
||||
)
|
||||
self.datac1_bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
|
||||
)
|
||||
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, 1)
|
||||
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
# DATAC0
|
||||
# SIGNALLING MODE 1 - Used for ACK/NACK - Payload 5 Bytes
|
||||
self.sig1_datac0_freedv, \
|
||||
self.sig1_datac0_bytes_per_frame, \
|
||||
self.sig1_datac0_bytes_out, \
|
||||
self.sig1_datac0_buffer, \
|
||||
self.sig1_datac0_nin = \
|
||||
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None)
|
||||
|
||||
# Datac3 - lower-bandwidth data frames
|
||||
self.datac3_freedv = ctypes.cast(
|
||||
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
|
||||
)
|
||||
self.c_lib.freedv_set_tuning_range(
|
||||
self.datac3_freedv,
|
||||
ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||
ctypes.c_float(static.TUNING_RANGE_FMAX),
|
||||
)
|
||||
self.datac3_bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
|
||||
)
|
||||
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, 1)
|
||||
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
|
||||
# FSK Long-distance Parity Code 0 - data frames
|
||||
self.fsk_ldpc_freedv_0 = ctypes.cast(
|
||||
codec2.api.freedv_open_advanced(
|
||||
# DATAC1
|
||||
self.dat0_datac1_freedv, \
|
||||
self.dat0_datac1_bytes_per_frame, \
|
||||
self.dat0_datac1_bytes_out, \
|
||||
self.dat0_datac1_buffer, \
|
||||
self.dat0_datac1_nin = \
|
||||
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC1, None)
|
||||
|
||||
# DATAC3
|
||||
self.dat0_datac3_freedv, \
|
||||
self.dat0_datac3_bytes_per_frame, \
|
||||
self.dat0_datac3_bytes_out, \
|
||||
self.dat0_datac3_buffer, \
|
||||
self.dat0_datac3_nin = \
|
||||
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC3, None)
|
||||
|
||||
# FSK LDPC - 0
|
||||
self.fsk_ldpc_freedv_0, \
|
||||
self.fsk_ldpc_bytes_per_frame_0, \
|
||||
self.fsk_ldpc_bytes_out_0, \
|
||||
self.fsk_ldpc_buffer_0, \
|
||||
self.fsk_ldpc_nin_0 = \
|
||||
self.init_codec2_mode(
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
self.fsk_ldpc_bytes_per_frame_0 = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8
|
||||
)
|
||||
self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer(
|
||||
self.fsk_ldpc_bytes_per_frame_0
|
||||
)
|
||||
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
||||
self.fsk_ldpc_buffer_0 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV
|
||||
)
|
||||
|
||||
# FSK Long-distance Parity Code 1 - data frames
|
||||
self.fsk_ldpc_freedv_1 = ctypes.cast(
|
||||
codec2.api.freedv_open_advanced(
|
||||
# FSK LDPC - 1
|
||||
self.fsk_ldpc_freedv_1, \
|
||||
self.fsk_ldpc_bytes_per_frame_1, \
|
||||
self.fsk_ldpc_bytes_out_1, \
|
||||
self.fsk_ldpc_buffer_1, \
|
||||
self.fsk_ldpc_nin_1 = \
|
||||
self.init_codec2_mode(
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
self.fsk_ldpc_bytes_per_frame_1 = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8
|
||||
)
|
||||
self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer(
|
||||
self.fsk_ldpc_bytes_per_frame_1
|
||||
)
|
||||
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
|
||||
self.fsk_ldpc_buffer_1 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
|
||||
# initial nin values
|
||||
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
|
||||
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
|
||||
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
|
||||
self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0)
|
||||
self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1)
|
||||
# self.log.debug("[MDM] RF: ",datac0_nin=self.datac0_nin)
|
||||
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
|
||||
if not TESTMODE:
|
||||
try:
|
||||
|
@ -242,10 +211,13 @@ class RF:
|
|||
|
||||
# --------------------------------------------INIT AND OPEN HAMLIB
|
||||
# Check how we want to control the radio
|
||||
# TODO: deprecated feature - we can remove this possibly
|
||||
if static.HAMLIB_RADIOCONTROL == "direct":
|
||||
import rig
|
||||
print("direct hamlib support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif static.HAMLIB_RADIOCONTROL == "rigctl":
|
||||
import rigctl as rig
|
||||
print("rigctl support deprecated - not usable anymore")
|
||||
sys.exit(1)
|
||||
elif static.HAMLIB_RADIOCONTROL == "rigctld":
|
||||
import rigctld as rig
|
||||
else:
|
||||
|
@ -272,20 +244,25 @@ class RF:
|
|||
)
|
||||
fft_thread.start()
|
||||
|
||||
audio_thread_datac0 = threading.Thread(
|
||||
target=self.audio_datac0, name="AUDIO_THREAD DATAC0", daemon=True
|
||||
audio_thread_sig0_datac0 = threading.Thread(
|
||||
target=self.audio_sig0_datac0, name="AUDIO_THREAD DATAC0 - 0", daemon=True
|
||||
)
|
||||
audio_thread_datac0.start()
|
||||
audio_thread_sig0_datac0.start()
|
||||
|
||||
audio_thread_datac1 = threading.Thread(
|
||||
target=self.audio_datac1, name="AUDIO_THREAD DATAC1", daemon=True
|
||||
audio_thread_sig1_datac0 = threading.Thread(
|
||||
target=self.audio_sig1_datac0, name="AUDIO_THREAD DATAC0 - 1", daemon=True
|
||||
)
|
||||
audio_thread_datac1.start()
|
||||
audio_thread_sig1_datac0.start()
|
||||
|
||||
audio_thread_datac3 = threading.Thread(
|
||||
target=self.audio_datac3, name="AUDIO_THREAD DATAC3", daemon=True
|
||||
audio_thread_dat0_datac1 = threading.Thread(
|
||||
target=self.audio_dat0_datac1, name="AUDIO_THREAD DATAC1", daemon=True
|
||||
)
|
||||
audio_thread_datac3.start()
|
||||
audio_thread_dat0_datac1.start()
|
||||
|
||||
audio_thread_dat0_datac3 = threading.Thread(
|
||||
target=self.audio_dat0_datac3, name="AUDIO_THREAD DATAC3", daemon=True
|
||||
)
|
||||
audio_thread_dat0_datac3.start()
|
||||
|
||||
if static.ENABLE_FSK:
|
||||
audio_thread_fsk_ldpc0 = threading.Thread(
|
||||
|
@ -303,6 +280,13 @@ class RF:
|
|||
)
|
||||
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")
|
||||
worker_received = threading.Thread(
|
||||
target=self.worker_received, name="WORKER_THREAD", daemon=True
|
||||
|
@ -321,7 +305,7 @@ class RF:
|
|||
depositing the data into the codec data buffers.
|
||||
"""
|
||||
while True:
|
||||
time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
# -----read
|
||||
data_in48k = bytes()
|
||||
with open(RXCHANNEL, "rb") as fifo:
|
||||
|
@ -335,15 +319,16 @@ class RF:
|
|||
|
||||
length_x = len(x)
|
||||
for data_buffer, receive in [
|
||||
(self.datac0_buffer, True),
|
||||
(self.datac1_buffer, RECEIVE_DATAC1),
|
||||
(self.datac3_buffer, RECEIVE_DATAC3),
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0),
|
||||
(self.sig1_datac0_buffer, RECEIVE_SIG1),
|
||||
(self.dat0_datac1_buffer, RECEIVE_DATAC1),
|
||||
(self.dat0_datac3_buffer, RECEIVE_DATAC3),
|
||||
# Not enabled yet.
|
||||
# (self.fsk_ldpc_buffer_0, static.ENABLE_FSK),
|
||||
# (self.fsk_ldpc_buffer_1, static.ENABLE_FSK),
|
||||
]:
|
||||
if (
|
||||
not data_buffer.nbuffer + length_x > data_buffer.size
|
||||
not (data_buffer.nbuffer + length_x) > data_buffer.size
|
||||
and receive
|
||||
):
|
||||
data_buffer.push(x)
|
||||
|
@ -351,14 +336,10 @@ class RF:
|
|||
def mkfifo_write_callback(self) -> None:
|
||||
"""Support testing by writing the audio data to a pipe."""
|
||||
while True:
|
||||
time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
|
||||
# -----write
|
||||
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
||||
# data_out48k = np.zeros(self.AUDIO_FRAMES_PER_BUFFER_RX, dtype=np.int16)
|
||||
pass
|
||||
|
||||
else:
|
||||
if len(self.modoutqueue) > 0 and not self.mod_out_locked:
|
||||
data_out48k = self.modoutqueue.popleft()
|
||||
# print(len(data_out48k))
|
||||
|
||||
|
@ -384,29 +365,44 @@ class RF:
|
|||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||
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
|
||||
if not static.TRANSMITTING:
|
||||
length_x = len(x)
|
||||
# TODO: Overriding this for testing purposes
|
||||
# if not static.TRANSMITTING:
|
||||
length_x = len(x)
|
||||
|
||||
# Avoid buffer overflow by filling only if buffer for
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.datac0_buffer, True, 0),
|
||||
(self.datac1_buffer, RECEIVE_DATAC1, 1),
|
||||
(self.datac3_buffer, RECEIVE_DATAC3, 2),
|
||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 3),
|
||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 4),
|
||||
]:
|
||||
if audiobuffer.nbuffer + length_x > audiobuffer.size:
|
||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||
elif receive:
|
||||
audiobuffer.push(x)
|
||||
# Avoid buffer overflow by filling only if buffer for
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0, 0),
|
||||
(self.sig1_datac0_buffer, RECEIVE_SIG1, 1),
|
||||
(self.dat0_datac1_buffer, RECEIVE_DATAC1, 2),
|
||||
(self.dat0_datac3_buffer, RECEIVE_DATAC3, 3),
|
||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
|
||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
|
||||
]:
|
||||
if (audiobuffer.nbuffer + length_x) > audiobuffer.size:
|
||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||
elif receive:
|
||||
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)
|
||||
self.fft_data = x
|
||||
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()
|
||||
self.fft_data = data_out48k
|
||||
|
||||
|
@ -430,18 +426,37 @@ class RF:
|
|||
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
|
||||
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
|
||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
jsondata = {"ptt": "True"}
|
||||
data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(data_out)
|
||||
# static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
# jsondata = {"ptt": "True"}
|
||||
# data_out = json.dumps(jsondata)
|
||||
# sock.SOCKET_QUEUE.put(data_out)
|
||||
|
||||
# Open codec2 instance
|
||||
self.MODE = mode
|
||||
freedv = open_codec2_instance(self.MODE)
|
||||
|
||||
# Get number of bytes per frame for mode
|
||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||
|
@ -466,11 +481,12 @@ class RF:
|
|||
)
|
||||
|
||||
# Add empty data to handle ptt toggle time
|
||||
data_delay_mseconds = 0 # milliseconds
|
||||
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
txbuffer = bytes(mod_out_silence)
|
||||
|
||||
#data_delay_mseconds = 0 # milliseconds
|
||||
#data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||
#mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
#txbuffer = bytes(mod_out_silence)
|
||||
# TODO: Disabled this one for testing
|
||||
txbuffer = bytes()
|
||||
self.log.debug(
|
||||
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
||||
)
|
||||
|
@ -534,8 +550,10 @@ class RF:
|
|||
txbuffer_48k = self.resampler.resample8_to_48(x)
|
||||
|
||||
# Explicitly lock our usage of mod_out_queue if needed
|
||||
# Deactivated for testing purposes
|
||||
self.mod_out_locked = False
|
||||
# This could avoid audio problems on slower CPU
|
||||
# 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
|
||||
|
@ -553,11 +571,11 @@ class RF:
|
|||
|
||||
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
|
||||
|
||||
while self.modoutqueue:
|
||||
time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
|
||||
static.PTT_STATE = self.hamlib.set_ptt(False)
|
||||
|
||||
|
@ -569,7 +587,6 @@ class RF:
|
|||
# After processing, set the locking state back to true to be prepared for next transmission
|
||||
self.mod_out_locked = True
|
||||
|
||||
self.c_lib.freedv_close(freedv)
|
||||
self.modem_transmit_queue.task_done()
|
||||
static.TRANSMITTING = False
|
||||
threading.Event().set()
|
||||
|
@ -585,13 +602,15 @@ class RF:
|
|||
freedv: ctypes.c_void_p,
|
||||
bytes_out,
|
||||
bytes_per_frame,
|
||||
state_buffer,
|
||||
mode_name,
|
||||
) -> int:
|
||||
"""
|
||||
De-modulate supplied audio stream with supplied codec2 instance.
|
||||
Decoded audio is placed into `bytes_out`.
|
||||
|
||||
:param buffer: Incoming audio
|
||||
:type buffer: codec2.audio_buffer
|
||||
:param audiobuffer: Incoming audio
|
||||
:type audiobuffer: codec2.audio_buffer
|
||||
:param nin: Number of frames codec2 is expecting
|
||||
:type nin: int
|
||||
:param freedv: codec2 instance
|
||||
|
@ -600,56 +619,180 @@ class RF:
|
|||
:type bytes_out: _type_
|
||||
:param bytes_per_frame: Number of bytes per frame
|
||||
: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
|
||||
:rtype: int
|
||||
"""
|
||||
nbytes = 0
|
||||
while self.stream.active:
|
||||
threading.Event().wait(0.01)
|
||||
while audiobuffer.nbuffer >= nin:
|
||||
# demodulate audio
|
||||
nbytes = codec2.api.freedv_rawdatarx(
|
||||
freedv, bytes_out, audiobuffer.buffer.ctypes
|
||||
)
|
||||
audiobuffer.pop(nin)
|
||||
nin = codec2.api.freedv_nin(freedv)
|
||||
if nbytes == bytes_per_frame:
|
||||
self.log.debug(
|
||||
"[MDM] [demod_audio] Pushing received data to received_queue"
|
||||
try:
|
||||
while self.stream.active:
|
||||
threading.Event().wait(0.01)
|
||||
while audiobuffer.nbuffer >= nin:
|
||||
# demodulate audio
|
||||
nbytes = codec2.api.freedv_rawdatarx(
|
||||
freedv, bytes_out, audiobuffer.buffer.ctypes
|
||||
)
|
||||
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
|
||||
# self.get_scatter(freedv)
|
||||
self.calculate_snr(freedv)
|
||||
# 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)
|
||||
nin = codec2.api.freedv_nin(freedv)
|
||||
if nbytes == bytes_per_frame:
|
||||
# process commands only if static.LISTEN = True
|
||||
if static.LISTEN:
|
||||
self.log.debug(
|
||||
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
|
||||
)
|
||||
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
|
||||
self.get_scatter(freedv)
|
||||
self.calculate_snr(freedv)
|
||||
state_buffer = []
|
||||
else:
|
||||
self.log.warning(
|
||||
"[MDM] [demod_audio] received frame but ignored processing",
|
||||
listen=static.LISTEN
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.warning("[MDM] [demod_audio] Stream not active anymore", e=e)
|
||||
return nin
|
||||
|
||||
def audio_datac0(self) -> None:
|
||||
"""Receive data encoded with datac0"""
|
||||
self.datac0_nin = self.demodulate_audio(
|
||||
self.datac0_buffer,
|
||||
self.datac0_nin,
|
||||
self.datac0_freedv,
|
||||
self.datac0_bytes_out,
|
||||
self.datac0_bytes_per_frame,
|
||||
def init_codec2_mode(self, mode, adv):
|
||||
"""
|
||||
Init codec2 and return some important parameters
|
||||
|
||||
Args:
|
||||
self:
|
||||
mode:
|
||||
adv:
|
||||
|
||||
Returns:
|
||||
c2instance, bytes_per_frame, bytes_out, audio_buffer, nin
|
||||
"""
|
||||
if adv:
|
||||
# FSK Long-distance Parity Code 1 - data frames
|
||||
c2instance = ctypes.cast(
|
||||
codec2.api.freedv_open_advanced(
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC,
|
||||
ctypes.byref(adv),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
else:
|
||||
|
||||
# create codec2 instance
|
||||
c2instance = ctypes.cast(
|
||||
codec2.api.freedv_open(mode), ctypes.c_void_p
|
||||
)
|
||||
|
||||
# set tuning range
|
||||
self.c_lib.freedv_set_tuning_range(
|
||||
c2instance,
|
||||
ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||
ctypes.c_float(static.TUNING_RANGE_FMAX),
|
||||
)
|
||||
|
||||
def audio_datac1(self) -> None:
|
||||
# get bytes per frame
|
||||
bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
|
||||
)
|
||||
|
||||
# create byte out buffer
|
||||
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
|
||||
|
||||
# set initial frames per burst
|
||||
codec2.api.freedv_set_frames_per_burst(c2instance, 1)
|
||||
|
||||
# init audio buffer
|
||||
audio_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
|
||||
|
||||
# get initial nin
|
||||
nin = codec2.api.freedv_nin(c2instance)
|
||||
|
||||
# Additional Datac0-specific information - these are not referenced anywhere else.
|
||||
# self.sig0_datac0_payload_per_frame = self.sig0_datac0_bytes_per_frame - 2
|
||||
# self.sig0_datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(
|
||||
# self.sig0_datac0_freedv
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(
|
||||
# self.sig0_datac0_freedv
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_preamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.sig0_datac0_freedv)
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_postamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.sig0_datac0_freedv)
|
||||
# )
|
||||
|
||||
# return values
|
||||
return c2instance, bytes_per_frame, bytes_out, audio_buffer, nin
|
||||
|
||||
def audio_sig0_datac0(self) -> None:
|
||||
"""Receive data encoded with datac0 - 0"""
|
||||
self.sig0_datac0_nin = self.demodulate_audio(
|
||||
self.sig0_datac0_buffer,
|
||||
self.sig0_datac0_nin,
|
||||
self.sig0_datac0_freedv,
|
||||
self.sig0_datac0_bytes_out,
|
||||
self.sig0_datac0_bytes_per_frame,
|
||||
SIG0_DATAC0_STATE,
|
||||
"sig0-datac0"
|
||||
)
|
||||
|
||||
def audio_sig1_datac0(self) -> None:
|
||||
"""Receive data encoded with datac0 - 1"""
|
||||
self.sig1_datac0_nin = self.demodulate_audio(
|
||||
self.sig1_datac0_buffer,
|
||||
self.sig1_datac0_nin,
|
||||
self.sig1_datac0_freedv,
|
||||
self.sig1_datac0_bytes_out,
|
||||
self.sig1_datac0_bytes_per_frame,
|
||||
SIG1_DATAC0_STATE,
|
||||
"sig1-datac0"
|
||||
)
|
||||
|
||||
def audio_dat0_datac1(self) -> None:
|
||||
"""Receive data encoded with datac1"""
|
||||
self.datac1_nin = self.demodulate_audio(
|
||||
self.datac1_buffer,
|
||||
self.datac1_nin,
|
||||
self.datac1_freedv,
|
||||
self.datac1_bytes_out,
|
||||
self.datac1_bytes_per_frame,
|
||||
self.dat0_datac1_nin = self.demodulate_audio(
|
||||
self.dat0_datac1_buffer,
|
||||
self.dat0_datac1_nin,
|
||||
self.dat0_datac1_freedv,
|
||||
self.dat0_datac1_bytes_out,
|
||||
self.dat0_datac1_bytes_per_frame,
|
||||
DAT0_DATAC1_STATE,
|
||||
"dat0-datac1"
|
||||
)
|
||||
|
||||
def audio_datac3(self) -> None:
|
||||
def audio_dat0_datac3(self) -> None:
|
||||
"""Receive data encoded with datac3"""
|
||||
self.datac3_nin = self.demodulate_audio(
|
||||
self.datac3_buffer,
|
||||
self.datac3_nin,
|
||||
self.datac3_freedv,
|
||||
self.datac3_bytes_out,
|
||||
self.datac3_bytes_per_frame,
|
||||
self.dat0_datac3_nin = self.demodulate_audio(
|
||||
self.dat0_datac3_buffer,
|
||||
self.dat0_datac3_nin,
|
||||
self.dat0_datac3_freedv,
|
||||
self.dat0_datac3_bytes_out,
|
||||
self.dat0_datac3_bytes_per_frame,
|
||||
DAT0_DATAC3_STATE,
|
||||
"dat0-datac3"
|
||||
)
|
||||
|
||||
def audio_fsk_ldpc_0(self) -> None:
|
||||
|
@ -675,9 +818,14 @@ class RF:
|
|||
def worker_transmit(self) -> None:
|
||||
"""Worker for FIFO queue for processing frames to be transmitted"""
|
||||
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()
|
||||
|
||||
self.log.debug("[MDM] worker_transmit", mode=data[0])
|
||||
# self.log.debug("[MDM] worker_transmit", mode=data[0])
|
||||
self.transmit(
|
||||
mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3]
|
||||
)
|
||||
|
@ -705,7 +853,6 @@ class RF:
|
|||
:rtype: float
|
||||
"""
|
||||
modemStats = codec2.MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
offset = round(modemStats.foff) * (-1)
|
||||
static.FREQ_OFFSET = offset
|
||||
|
@ -723,28 +870,34 @@ class RF:
|
|||
return
|
||||
|
||||
modemStats = codec2.MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
ctypes.cast(
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
|
||||
scatterdata = []
|
||||
scatterdata_small = []
|
||||
for i in range(codec2.MODEM_STATS_NC_MAX):
|
||||
for j in range(codec2.MODEM_STATS_NR_MAX):
|
||||
# check if odd or not to get every 2nd item for x
|
||||
if (j % 2) == 0:
|
||||
xsymbols = round(modemStats.rx_symbols[i][j] / 1000)
|
||||
ysymbols = round(modemStats.rx_symbols[i][j + 1] / 1000)
|
||||
# check if value 0.0 or has real data
|
||||
if xsymbols != 0.0 and ysymbols != 0.0:
|
||||
scatterdata.append({"x": xsymbols, "y": ysymbols})
|
||||
# original function before itertool
|
||||
#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]}")
|
||||
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)})
|
||||
|
||||
# Send all the data if we have too-few samples, otherwise send a sampling
|
||||
if 150 > len(scatterdata) > 0:
|
||||
static.SCATTER = scatterdata
|
||||
else:
|
||||
# only take every tenth data point
|
||||
scatterdata_small = scatterdata[::10]
|
||||
static.SCATTER = scatterdata_small
|
||||
static.SCATTER = scatterdata[::10]
|
||||
|
||||
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
||||
"""
|
||||
|
@ -769,16 +922,30 @@ class RF:
|
|||
|
||||
snr = round(modem_stats_snr, 1)
|
||||
self.log.info("[MDM] calculate_snr: ", snr=snr)
|
||||
# static.SNR = np.clip(snr, 0, 255) # limit to max value of 255
|
||||
static.SNR = np.clip(
|
||||
snr, -128, 128
|
||||
) # limit to max value of -128/128 as a possible fix of #188
|
||||
static.SNR = snr
|
||||
#static.SNR = np.clip(
|
||||
# snr, -127, 127
|
||||
#) # limit to max value of -128/128 as a possible fix of #188
|
||||
return static.SNR
|
||||
except Exception as err:
|
||||
self.log.error(f"[MDM] calculate_snr: Exception: {err}")
|
||||
static.SNR = 0
|
||||
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:
|
||||
"""
|
||||
Request information about the current state of the radio via hamlib
|
||||
|
@ -788,10 +955,11 @@ class RF:
|
|||
- static.HAMLIB_BANDWIDTH
|
||||
"""
|
||||
while True:
|
||||
threading.Event().wait(0.5)
|
||||
threading.Event().wait(0.25)
|
||||
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
||||
static.HAMLIB_MODE = self.hamlib.get_mode()
|
||||
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
||||
static.HAMLIB_STATUS = self.hamlib.get_status()
|
||||
|
||||
def calculate_fft(self) -> None:
|
||||
"""
|
||||
|
@ -801,8 +969,11 @@ class RF:
|
|||
# Initialize channel_busy_delay counter
|
||||
channel_busy_delay = 0
|
||||
|
||||
# Initialize dbfs counter
|
||||
rms_counter = 0
|
||||
|
||||
while True:
|
||||
# time.sleep(0.01)
|
||||
# threading.Event().wait(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
# WE NEED TO OPTIMIZE THIS!
|
||||
|
||||
|
@ -828,19 +999,60 @@ class RF:
|
|||
# Have to do this when we are not transmitting so our
|
||||
# own sending data will not affect this too much
|
||||
if not static.TRANSMITTING:
|
||||
dfft[dfft > avg + 10] = 100
|
||||
dfft[dfft > avg + 15] = 100
|
||||
|
||||
# Calculate audio max value
|
||||
# static.AUDIO_RMS = np.amax(self.fft_data)
|
||||
# Calculate audio dbfs
|
||||
# https://stackoverflow.com/a/9763652
|
||||
# calculate dbfs every 50 cycles for reducing CPU load
|
||||
rms_counter += 1
|
||||
if rms_counter > 50:
|
||||
d = np.frombuffer(self.fft_data, np.int16).astype(np.float32)
|
||||
# calculate RMS and then dBFS
|
||||
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
||||
# 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)))
|
||||
if rms == 0:
|
||||
raise ZeroDivisionError
|
||||
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
|
||||
|
||||
# Convert data to int to decrease size
|
||||
dfft = dfft.astype(int)
|
||||
|
||||
# Create list of dfft for later pushing to static.FFT
|
||||
dfftlist = dfft.tolist()
|
||||
|
||||
# Reduce area where the busy detection is enabled
|
||||
# We want to have this in correlation with mode bandwidth
|
||||
# TODO: This is not correctly and needs to be checked for correct maths
|
||||
# dfftlist[0:1] = 10,15Hz
|
||||
# Bandwidth[Hz] / 10,15
|
||||
# narrowband = 563Hz = 56
|
||||
# wideband = 1700Hz = 167
|
||||
# 1500Hz = 148
|
||||
# 2700Hz = 266
|
||||
# 3200Hz = 315
|
||||
|
||||
# define the area, we are detecting busy state
|
||||
dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231]
|
||||
|
||||
# Check for signals higher than average by checking for "100"
|
||||
# If we have a signal, increment our channel_busy delay counter
|
||||
# so we have a smoother state toggle
|
||||
if np.sum(dfft[dfft > avg + 10]) >= 300 and not static.TRANSMITTING:
|
||||
if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING:
|
||||
static.CHANNEL_BUSY = True
|
||||
# Limit delay counter to a maximun of 50. The higher this value,
|
||||
# Limit delay counter to a maximum of 200. The higher this value,
|
||||
# the longer we will wait until releasing state
|
||||
channel_busy_delay = min(channel_busy_delay + 5, 50)
|
||||
channel_busy_delay = min(channel_busy_delay + 10, 200)
|
||||
else:
|
||||
# Decrement channel busy counter if no signal has been detected.
|
||||
channel_busy_delay = max(channel_busy_delay - 1, 0)
|
||||
|
@ -848,11 +1060,7 @@ class RF:
|
|||
if channel_busy_delay == 0:
|
||||
static.CHANNEL_BUSY = False
|
||||
|
||||
# Round data to decrease size
|
||||
dfft = np.around(dfft, 0)
|
||||
dfftlist = dfft.tolist()
|
||||
|
||||
static.FFT = dfftlist[:320] # 320 --> bandwidth 3000
|
||||
static.FFT = dfftlist[:315] # 315 --> bandwidth 3200
|
||||
except Exception as err:
|
||||
self.log.error(f"[MDM] calculate_fft: Exception: {err}")
|
||||
self.log.debug("[MDM] Setting fft=0")
|
||||
|
@ -870,8 +1078,8 @@ class RF:
|
|||
frames_per_burst = min(frames_per_burst, 1)
|
||||
frames_per_burst = max(frames_per_burst, 5)
|
||||
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.dat0_datac3_freedv, frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst)
|
||||
|
||||
|
||||
|
@ -932,8 +1140,31 @@ def set_audio_volume(datalist, volume: float) -> np.int16:
|
|||
:return: Scaled audio samples
|
||||
:rtype: np.int16
|
||||
"""
|
||||
# make sure we have float as data type to avoid crash
|
||||
try:
|
||||
volume = float(volume)
|
||||
except Exception as e:
|
||||
print(f"[MDM] changing audio volume failed with error: {e}")
|
||||
volume = 100.0
|
||||
|
||||
# Clip volume provided to acceptable values
|
||||
volume = np.clip(volume, 0, 200) # limit to max value of 255
|
||||
# Scale samples by the ratio of volume / 100.0
|
||||
data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore
|
||||
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
|
||||
RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE)
|
||||
|
||||
# Commands we want to send to rigctld
|
||||
RIGCTLD_COMMAND_QUEUE = queue.Queue()
|
278
tnc/rig.py
278
tnc/rig.py
|
@ -1,278 +0,0 @@
|
|||
import atexit
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import structlog
|
||||
|
||||
mainlog = structlog.get_logger("rig")
|
||||
|
||||
# set global hamlib version
|
||||
hamlib_version = 0
|
||||
|
||||
# append local search path
|
||||
# check if we are running in a pyinstaller environment
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
sys.path.append(getattr(sys, "_MEIPASS"))
|
||||
else:
|
||||
sys.path.append(os.path.abspath("."))
|
||||
|
||||
# try importing hamlib
|
||||
try:
|
||||
# get python version
|
||||
python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}"
|
||||
|
||||
# installation path for Ubuntu 20.04 LTS python modules
|
||||
# sys.path.append(f"/usr/local/lib/python{python_version}/site-packages")
|
||||
|
||||
# installation path for Ubuntu 20.10 +
|
||||
sys.path.append("/usr/local/lib/")
|
||||
|
||||
# installation path for Suse
|
||||
sys.path.append(f"/usr/local/lib64/python{python_version}/site-packages")
|
||||
|
||||
# everything else... not nice, but an attempt to see how it's running within app bundle
|
||||
# this is not needed as python will be shipped with app bundle
|
||||
sys.path.append("/usr/local/lib/python3.6/site-packages")
|
||||
sys.path.append("/usr/local/lib/python3.7/site-packages")
|
||||
sys.path.append("/usr/local/lib/python3.8/site-packages")
|
||||
sys.path.append("/usr/local/lib/python3.9/site-packages")
|
||||
sys.path.append("/usr/local/lib/python3.10/site-packages")
|
||||
|
||||
sys.path.append("lib/hamlib/linux/python3.8/site-packages")
|
||||
import Hamlib
|
||||
|
||||
# https://stackoverflow.com/a/4703409
|
||||
hamlib_version = re.findall(r"[-+]?\d*\.?\d+|\d+", Hamlib.cvar.hamlib_version)
|
||||
hamlib_version = float(hamlib_version[0])
|
||||
|
||||
min_hamlib_version = 4.1
|
||||
if hamlib_version > min_hamlib_version:
|
||||
mainlog.info("[RIG] Hamlib found", version=hamlib_version)
|
||||
else:
|
||||
mainlog.warning(
|
||||
"[RIG] Hamlib outdated", found=hamlib_version, recommend=min_hamlib_version
|
||||
)
|
||||
except Exception as err:
|
||||
mainlog.warning("[RIG] Python Hamlib binding not found", error=err)
|
||||
try:
|
||||
mainlog.warning("[RIG] Trying to open rigctl")
|
||||
rigctl = subprocess.Popen(
|
||||
"rigctl -V",
|
||||
shell=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
hamlib_version = rigctl.stdout.readline()
|
||||
hamlib_version = hamlib_version.split(" ")
|
||||
|
||||
if hamlib_version[1] == "Hamlib":
|
||||
mainlog.warning(
|
||||
"[RIG] Rigctl found! Please try using this", version=hamlib_version[2]
|
||||
)
|
||||
sys.exit()
|
||||
else:
|
||||
raise Exception
|
||||
except Exception as err1:
|
||||
mainlog.critical("[RIG] HAMLIB NOT INSTALLED", error=err1)
|
||||
hamlib_version = 0
|
||||
sys.exit()
|
||||
|
||||
|
||||
class radio:
|
||||
""" """
|
||||
|
||||
log = structlog.get_logger(__name__)
|
||||
|
||||
def __init__(self):
|
||||
self.devicename = ""
|
||||
self.devicenumber = ""
|
||||
self.deviceport = ""
|
||||
self.serialspeed = ""
|
||||
self.hamlib_ptt_type = ""
|
||||
self.my_rig = ""
|
||||
self.pttport = ""
|
||||
self.data_bits = ""
|
||||
self.stop_bits = ""
|
||||
self.handshake = ""
|
||||
|
||||
def open_rig(
|
||||
self,
|
||||
devicename,
|
||||
deviceport,
|
||||
hamlib_ptt_type,
|
||||
serialspeed,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_port,
|
||||
rigctld_ip,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
devicename:
|
||||
deviceport:
|
||||
hamlib_ptt_type:
|
||||
serialspeed:
|
||||
pttport:
|
||||
data_bits:
|
||||
stop_bits:
|
||||
handshake:
|
||||
rigctld_port:
|
||||
rigctld_ip:
|
||||
|
||||
"""
|
||||
self.devicename = devicename
|
||||
self.deviceport = str(deviceport)
|
||||
# we need to ensure this is a str, otherwise set_conf functions are crashing
|
||||
self.serialspeed = str(serialspeed)
|
||||
self.hamlib_ptt_type = str(hamlib_ptt_type)
|
||||
self.pttport = str(pttport)
|
||||
self.data_bits = str(data_bits)
|
||||
self.stop_bits = str(stop_bits)
|
||||
self.handshake = str(handshake)
|
||||
|
||||
# try to init hamlib
|
||||
try:
|
||||
Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
|
||||
|
||||
# get devicenumber by looking for deviceobject in Hamlib module
|
||||
try:
|
||||
self.devicenumber = int(getattr(Hamlib, self.devicename))
|
||||
except Exception:
|
||||
self.log.error("[RIG] Hamlib: rig not supported...")
|
||||
self.devicenumber = 0
|
||||
|
||||
self.my_rig = Hamlib.Rig(self.devicenumber)
|
||||
self.my_rig.set_conf("rig_pathname", self.deviceport)
|
||||
self.my_rig.set_conf("retry", "5")
|
||||
self.my_rig.set_conf("serial_speed", self.serialspeed)
|
||||
self.my_rig.set_conf("serial_handshake", self.handshake)
|
||||
self.my_rig.set_conf("stop_bits", self.stop_bits)
|
||||
self.my_rig.set_conf("data_bits", self.data_bits)
|
||||
self.my_rig.set_conf("ptt_pathname", self.pttport)
|
||||
|
||||
if self.hamlib_ptt_type == "RIG":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG
|
||||
self.my_rig.set_conf("ptt_type", "RIG")
|
||||
|
||||
elif self.hamlib_ptt_type == "USB":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PORT_USB
|
||||
self.my_rig.set_conf("ptt_type", "USB")
|
||||
|
||||
elif self.hamlib_ptt_type == "DTR-H":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR
|
||||
self.my_rig.set_conf("dtr_state", "HIGH")
|
||||
self.my_rig.set_conf("ptt_type", "DTR")
|
||||
|
||||
elif self.hamlib_ptt_type == "DTR-L":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR
|
||||
self.my_rig.set_conf("dtr_state", "LOW")
|
||||
self.my_rig.set_conf("ptt_type", "DTR")
|
||||
|
||||
elif self.hamlib_ptt_type == "RTS":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_RTS
|
||||
self.my_rig.set_conf("dtr_state", "OFF")
|
||||
self.my_rig.set_conf("ptt_type", "RTS")
|
||||
|
||||
elif self.hamlib_ptt_type == "PARALLEL":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_PARALLEL
|
||||
|
||||
elif self.hamlib_ptt_type == "MICDATA":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG_MICDATA
|
||||
|
||||
elif self.hamlib_ptt_type == "CM108":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_CM108
|
||||
|
||||
elif self.hamlib_ptt_type == "RIG_PTT_NONE":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE
|
||||
|
||||
else: # self.hamlib_ptt_type == "RIG_PTT_NONE":
|
||||
self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE
|
||||
|
||||
self.log.info(
|
||||
"[RIG] Opening...",
|
||||
device=self.devicenumber,
|
||||
path=self.my_rig.get_conf("rig_pathname"),
|
||||
serial_speed=self.my_rig.get_conf("serial_speed"),
|
||||
serial_handshake=self.my_rig.get_conf("serial_handshake"),
|
||||
stop_bits=self.my_rig.get_conf("stop_bits"),
|
||||
data_bits=self.my_rig.get_conf("data_bits"),
|
||||
ptt_pathname=self.my_rig.get_conf("ptt_pathname"),
|
||||
)
|
||||
|
||||
self.my_rig.open()
|
||||
atexit.register(self.my_rig.close)
|
||||
|
||||
try:
|
||||
# lets determine the error message when opening rig
|
||||
error = str(Hamlib.rigerror(self.my_rig.error_status)).splitlines()
|
||||
error = error[1].split("err=")
|
||||
error = error[1]
|
||||
|
||||
if error == "Permission denied":
|
||||
self.log.error("[RIG] Hamlib has no permissions", e=error)
|
||||
help_url = "https://github.com/DJ2LS/FreeDATA/wiki/UBUNTU-Manual-installation#1-permissions"
|
||||
self.log.error("[RIG] HELP:", check=help_url)
|
||||
except Exception:
|
||||
self.log.info("[RIG] Hamlib device opened", status="SUCCESS")
|
||||
|
||||
# set ptt to false if ptt is stuck for some reason
|
||||
self.set_ptt(False)
|
||||
|
||||
# set rig mode to USB
|
||||
# temporarly outcommented because of possible problems.
|
||||
# self.my_rig.set_mode(Hamlib.RIG_MODE_USB)
|
||||
# self.my_rig.set_mode(Hamlib.RIG_MODE_PKTUSB)
|
||||
return True
|
||||
|
||||
except Exception as err2:
|
||||
self.log.error(
|
||||
"[RIG] Hamlib - can't open rig", error=err2, e=sys.exc_info()[0]
|
||||
)
|
||||
return False
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
return int(self.my_rig.get_freq())
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
(hamlib_mode, bandwidth) = self.my_rig.get_mode()
|
||||
return Hamlib.rig_strrmode(hamlib_mode)
|
||||
|
||||
def get_bandwidth(self):
|
||||
""" """
|
||||
(hamlib_mode, bandwidth) = self.my_rig.get_mode()
|
||||
return bandwidth
|
||||
|
||||
# not needed yet beacuse of some possible problems
|
||||
# def set_mode(self, mode):
|
||||
# return 0
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
return self.my_rig.get_ptt()
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if state:
|
||||
self.my_rig.set_ptt(Hamlib.RIG_VFO_CURR, 1)
|
||||
else:
|
||||
self.my_rig.set_ptt(Hamlib.RIG_VFO_CURR, 0)
|
||||
return state
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
self.my_rig.close()
|
214
tnc/rigctl.py
214
tnc/rigctl.py
|
@ -1,214 +0,0 @@
|
|||
# Intially created by Franco Spinelli, IW2DHW, 01/2022
|
||||
# Updated by DJ2LS
|
||||
#
|
||||
# versione mia di rig.py per gestire Ft897D tramite rigctl e senza
|
||||
# fare alcun riferimento alla configurazione
|
||||
#
|
||||
# e' una pezza clamorosa ma serve per poter provare on-air il modem
|
||||
#
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import structlog
|
||||
|
||||
# for rig_model -> rig_number only
|
||||
|
||||
# set global hamlib version
|
||||
hamlib_version = 0
|
||||
|
||||
|
||||
class radio:
|
||||
""" """
|
||||
|
||||
log = structlog.get_logger("radio (rigctl)")
|
||||
|
||||
def __init__(self):
|
||||
self.devicename = ""
|
||||
self.devicenumber = ""
|
||||
self.deviceport = ""
|
||||
self.serialspeed = ""
|
||||
self.hamlib_ptt_type = ""
|
||||
self.my_rig = ""
|
||||
self.pttport = ""
|
||||
self.data_bits = ""
|
||||
self.stop_bits = ""
|
||||
self.handshake = ""
|
||||
self.cmd = ""
|
||||
|
||||
def open_rig(
|
||||
self,
|
||||
devicename,
|
||||
deviceport,
|
||||
hamlib_ptt_type,
|
||||
serialspeed,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
devicename:
|
||||
deviceport:
|
||||
hamlib_ptt_type:
|
||||
serialspeed:
|
||||
pttport:
|
||||
data_bits:
|
||||
stop_bits:
|
||||
handshake:
|
||||
rigctld_ip:
|
||||
rigctld_port:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.devicename = devicename
|
||||
self.deviceport = deviceport
|
||||
# we need to ensure this is a str, otherwise set_conf functions are crashing
|
||||
self.serialspeed = str(serialspeed)
|
||||
self.hamlib_ptt_type = hamlib_ptt_type
|
||||
self.pttport = pttport
|
||||
self.data_bits = data_bits
|
||||
self.stop_bits = stop_bits
|
||||
self.handshake = handshake
|
||||
|
||||
# check if we are running in a pyinstaller environment
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
sys.path.append(getattr(sys, "_MEIPASS"))
|
||||
else:
|
||||
sys.path.append(os.path.abspath("."))
|
||||
|
||||
# get devicenumber by looking for deviceobject in Hamlib module
|
||||
try:
|
||||
import Hamlib
|
||||
|
||||
self.devicenumber = int(getattr(Hamlib, self.devicename))
|
||||
except Exception as err:
|
||||
if int(self.devicename):
|
||||
self.devicenumber = int(self.devicename)
|
||||
else:
|
||||
self.devicenumber = 6 # dummy
|
||||
self.log.warning("[RIGCTL] Radio not found. Using DUMMY!", error=err)
|
||||
|
||||
# set deviceport to dummy port, if we selected dummy model
|
||||
if self.devicenumber in {1, 6}:
|
||||
self.deviceport = "/dev/ttyUSB0"
|
||||
|
||||
print(self.devicenumber, self.deviceport, self.serialspeed)
|
||||
|
||||
# select precompiled executable for win32/win64 rigctl
|
||||
# this is really a hack...somewhen we need a native hamlib integration for windows
|
||||
if sys.platform in ["win32", "win64"]:
|
||||
self.cmd = (
|
||||
app_path
|
||||
+ "lib\\hamlib\\"
|
||||
+ sys.platform
|
||||
+ (
|
||||
f"\\rigctl -m {self.devicenumber} "
|
||||
f"-r {self.deviceport} "
|
||||
f"-s {int(self.serialspeed)} "
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
self.cmd = "rigctl -m %d -r %s -s %d " % (
|
||||
self.devicenumber,
|
||||
self.deviceport,
|
||||
int(self.serialspeed),
|
||||
)
|
||||
|
||||
# eseguo semplicemente rigctl con il solo comando T 1 o T 0 per
|
||||
# il set e t per il get
|
||||
|
||||
# set ptt to false if ptt is stuck for some reason
|
||||
self.set_ptt(False)
|
||||
return True
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
cmd = f"{self.cmd} f"
|
||||
sw_proc = subprocess.Popen(
|
||||
cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True
|
||||
)
|
||||
time.sleep(0.5)
|
||||
freq = sw_proc.communicate()[0]
|
||||
# print("get_frequency", freq, sw_proc.communicate())
|
||||
try:
|
||||
return int(freq)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
# (hamlib_mode, bandwidth) = self.my_rig.get_mode()
|
||||
# return Hamlib.rig_strrmode(hamlib_mode)
|
||||
try:
|
||||
return "PKTUSB"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_bandwidth(self):
|
||||
""" """
|
||||
# (hamlib_mode, bandwidth) = self.my_rig.get_mode()
|
||||
bandwidth = 2700
|
||||
|
||||
try:
|
||||
return bandwidth
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# non usata
|
||||
return 0
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
cmd = f"{self.cmd} t"
|
||||
sw_proc = subprocess.Popen(
|
||||
cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True
|
||||
)
|
||||
time.sleep(0.5)
|
||||
status = sw_proc.communicate()[0]
|
||||
|
||||
try:
|
||||
return status
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def set_ptt(self, state):
|
||||
"""
|
||||
|
||||
Args:
|
||||
state:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
cmd = f"{self.cmd} T "
|
||||
print("set_ptt", state)
|
||||
cmd = f"{cmd}1" if state else f"{cmd}0"
|
||||
print("set_ptt", cmd)
|
||||
|
||||
sw_proc = subprocess.Popen(cmd, shell=True, text=True)
|
||||
try:
|
||||
return state
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def close_rig(self):
|
||||
""" """
|
||||
# self.my_rig.close()
|
||||
return
|
224
tnc/rigctld.py
224
tnc/rigctld.py
|
@ -1,13 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
# class taken from darsidelemm
|
||||
# class taken from darksidelemm
|
||||
# rigctl - https://github.com/darksidelemm/rotctld-web-gui/blob/master/rotatorgui.py#L35
|
||||
#
|
||||
# modified and adjusted to FreeDATA needs by DJ2LS
|
||||
|
||||
import contextlib
|
||||
import socket
|
||||
import time
|
||||
|
||||
import structlog
|
||||
import threading
|
||||
|
||||
# set global hamlib version
|
||||
hamlib_version = 0
|
||||
|
@ -16,20 +17,24 @@ hamlib_version = 0
|
|||
class radio:
|
||||
"""rigctld (hamlib) communication class"""
|
||||
|
||||
# Note: This is a massive hack.
|
||||
|
||||
log = structlog.get_logger("radio (rigctld)")
|
||||
|
||||
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
|
||||
"""Open a connection to rigctld, and test it for validity"""
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# self.sock.settimeout(timeout)
|
||||
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.port = port
|
||||
self.connection_attempts = 5
|
||||
|
||||
# class wide variable for some parameters
|
||||
self.bandwidth = ''
|
||||
self.frequency = ''
|
||||
self.mode = ''
|
||||
|
||||
def open_rig(
|
||||
self,
|
||||
devicename,
|
||||
|
@ -63,42 +68,79 @@ class radio:
|
|||
self.hostname = rigctld_ip
|
||||
self.port = int(rigctld_port)
|
||||
|
||||
if self.connect():
|
||||
self.log.debug("Rigctl initialized")
|
||||
#_ptt_connect = self.ptt_connect()
|
||||
#_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
|
||||
|
||||
self.log.error(
|
||||
"[RIGCTLD] Can't connect to rigctld!", ip=self.hostname, port=self.port
|
||||
"[RIGCTLD] Can't connect!", ip=self.hostname, port=self.port
|
||||
)
|
||||
return False
|
||||
|
||||
def connect(self):
|
||||
def ptt_connect(self):
|
||||
"""Connect to rigctld instance"""
|
||||
if not self.connected:
|
||||
try:
|
||||
self.connection = socket.create_connection((self.hostname, self.port))
|
||||
self.connected = True
|
||||
self.log.info(
|
||||
"[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port
|
||||
)
|
||||
return True
|
||||
except Exception as err:
|
||||
# ConnectionRefusedError: [Errno 111] Connection refused
|
||||
self.close_rig()
|
||||
self.log.warning(
|
||||
"[RIGCTLD] Connection to rigctld refused! Reconnect...",
|
||||
ip=self.hostname,
|
||||
port=self.port,
|
||||
e=err,
|
||||
)
|
||||
return False
|
||||
while True:
|
||||
|
||||
if not self.ptt_connected:
|
||||
try:
|
||||
self.ptt_connection = socket.create_connection((self.hostname, self.port))
|
||||
self.ptt_connected = True
|
||||
self.log.info(
|
||||
"[RIGCTLD] Connected PTT 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] PTT Reconnect...",
|
||||
ip=self.hostname,
|
||||
port=self.port,
|
||||
e=err,
|
||||
)
|
||||
|
||||
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):
|
||||
""" """
|
||||
self.sock.close()
|
||||
self.connected = False
|
||||
self.ptt_sock.close()
|
||||
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,
|
||||
and return the return value.
|
||||
|
||||
|
@ -106,9 +148,9 @@ class radio:
|
|||
command:
|
||||
|
||||
"""
|
||||
if self.connected:
|
||||
if self.ptt_connected:
|
||||
try:
|
||||
self.connection.sendall(command + b"\n")
|
||||
self.ptt_connection.sendall(command + b"\n")
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"[RIGCTLD] Command not executed!",
|
||||
|
@ -116,10 +158,33 @@ class radio:
|
|||
ip=self.hostname,
|
||||
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:
|
||||
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:
|
||||
self.log.warning(
|
||||
"[RIGCTLD] No command response!",
|
||||
|
@ -127,47 +192,62 @@ class radio:
|
|||
ip=self.hostname,
|
||||
port=self.port,
|
||||
)
|
||||
self.connected = False
|
||||
else:
|
||||
|
||||
# reconnecting....
|
||||
time.sleep(0.5)
|
||||
self.connect()
|
||||
|
||||
self.data_connected = False
|
||||
return b""
|
||||
|
||||
def get_status(self):
|
||||
""" """
|
||||
return "connected" if self.data_connected and self.ptt_connected else "unknown/disconnected"
|
||||
|
||||
def get_mode(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = self.send_data_command(b"m", True)
|
||||
data = data.split(b"\n")
|
||||
mode = data[0]
|
||||
return mode.decode("utf-8")
|
||||
data = data[0].decode("utf-8")
|
||||
if 'RPRT' not in data:
|
||||
try:
|
||||
data = int(data)
|
||||
except ValueError:
|
||||
self.mode = str(data)
|
||||
|
||||
return self.mode
|
||||
except Exception:
|
||||
return 0
|
||||
return self.mode
|
||||
|
||||
def get_bandwidth(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = self.send_data_command(b"m", True)
|
||||
data = data.split(b"\n")
|
||||
bandwidth = data[1]
|
||||
return bandwidth.decode("utf-8")
|
||||
data = data[1].decode("utf-8")
|
||||
|
||||
if 'RPRT' not in data and data not in ['']:
|
||||
with contextlib.suppress(ValueError):
|
||||
self.bandwidth = int(data)
|
||||
return self.bandwidth
|
||||
except Exception:
|
||||
return 0
|
||||
return self.bandwidth
|
||||
|
||||
def get_frequency(self):
|
||||
""" """
|
||||
try:
|
||||
frequency = self.send_command(b"f")
|
||||
return frequency.decode("utf-8")
|
||||
data = self.send_data_command(b"f", True)
|
||||
data = data.decode("utf-8")
|
||||
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
|
||||
return self.frequency
|
||||
except Exception:
|
||||
return 0
|
||||
return self.frequency
|
||||
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
try:
|
||||
return self.send_command(b"t")
|
||||
return self.send_command(b"t", True)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
@ -182,9 +262,39 @@ class radio:
|
|||
"""
|
||||
try:
|
||||
if state:
|
||||
self.send_command(b"T 1")
|
||||
self.send_ptt_command(b"T 1", False)
|
||||
else:
|
||||
self.send_command(b"T 0")
|
||||
self.send_ptt_command(b"T 0", False)
|
||||
return state
|
||||
except Exception:
|
||||
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
|
||||
|
||||
def set_bandwidth(self):
|
||||
""" """
|
||||
return None
|
||||
def set_mode(self, mode):
|
||||
"""
|
||||
|
||||
|
@ -41,6 +44,26 @@ class radio:
|
|||
"""
|
||||
return None
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return None
|
||||
def get_status(self):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return "connected"
|
||||
def get_ptt(self):
|
||||
""" """
|
||||
return None
|
||||
|
|
74
tnc/selftest.py
Normal file
74
tnc/selftest.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
simple TNC self tests
|
||||
"""
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||
import sys
|
||||
import structlog
|
||||
log = structlog.get_logger("selftest")
|
||||
|
||||
|
||||
class TEST():
|
||||
def __init__(self):
|
||||
log.info("[selftest] running self tests...")
|
||||
if self.run_tests():
|
||||
log.info("[selftest] passed -> starting TNC")
|
||||
else:
|
||||
log.error("[selftest] failed -> closing TNC")
|
||||
sys.exit(0)
|
||||
|
||||
def run_tests(self):
|
||||
return bool(
|
||||
self.check_imports()
|
||||
and self.check_sounddevice()
|
||||
and self.check_helpers()
|
||||
)
|
||||
|
||||
|
||||
|
||||
def check_imports(self):
|
||||
try:
|
||||
import argparse
|
||||
import atexit
|
||||
import multiprocessing
|
||||
import os
|
||||
import signal
|
||||
import socketserver
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import structlog
|
||||
import crcengine
|
||||
import ctypes
|
||||
import glob
|
||||
import enum
|
||||
import numpy
|
||||
import sounddevice
|
||||
return True
|
||||
except Exception as e:
|
||||
log.info("[selftest] [check_imports] [failed]", e=e)
|
||||
return False
|
||||
|
||||
def check_sounddevice(self):
|
||||
try:
|
||||
import audio
|
||||
audio.get_audio_devices()
|
||||
return True
|
||||
except Exception as e:
|
||||
log.info("[selftest] [check_sounddevice] [failed]", e=e)
|
||||
return False
|
||||
|
||||
def check_helpers(self):
|
||||
try:
|
||||
import helpers
|
||||
valid_crc24 = "f86ed0"
|
||||
if helpers.get_crc_24(b"test").hex() == valid_crc24:
|
||||
return True
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
except Exception as e:
|
||||
log.info("[selftest] [check_helpers] [failed]", e=e)
|
||||
return False
|
245
tnc/sock.py
245
tnc/sock.py
|
@ -24,13 +24,14 @@ import socketserver
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import wave
|
||||
|
||||
import helpers
|
||||
import static
|
||||
import structlog
|
||||
import ujson as json
|
||||
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()
|
||||
DAEMON_QUEUE = queue.Queue()
|
||||
|
@ -76,7 +77,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
if data != tempdata:
|
||||
tempdata = data
|
||||
SOCKET_QUEUE.put(data)
|
||||
time.sleep(0.5)
|
||||
threading.Event().wait(0.5)
|
||||
|
||||
while not SOCKET_QUEUE.empty():
|
||||
data = SOCKET_QUEUE.get()
|
||||
|
@ -84,20 +85,23 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
sock_data += b"\n" # append line limiter
|
||||
|
||||
# send data to all clients
|
||||
# try:
|
||||
for client in CONNECTED_CLIENTS:
|
||||
try:
|
||||
client.send(sock_data)
|
||||
except Exception as err:
|
||||
self.log.info("[SCK] Connection lost", e=err)
|
||||
self.connection_alive = False
|
||||
try:
|
||||
for client in CONNECTED_CLIENTS:
|
||||
try:
|
||||
client.send(sock_data)
|
||||
except Exception as 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
|
||||
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
|
||||
static.SCATTER = []
|
||||
# we want to display INFO messages only once
|
||||
static.INFO = []
|
||||
# self.request.sendall(sock_data)
|
||||
time.sleep(0.15)
|
||||
threading.Event().wait(0.15)
|
||||
|
||||
def receive_from_client(self):
|
||||
"""
|
||||
|
@ -132,7 +136,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
# we might improve this by only processing one command or
|
||||
# doing some kind of selection to determin which commands need to be dropped
|
||||
# and which one can be processed during a running transmission
|
||||
time.sleep(3)
|
||||
threading.Event().wait(0.5)
|
||||
|
||||
# finally delete our rx buffer to be ready for new commands
|
||||
data = bytes()
|
||||
|
@ -169,7 +173,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
|||
|
||||
# keep connection alive until we close it
|
||||
while self.connection_alive and not CLOSE_SIGNAL:
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
|
||||
def finish(self):
|
||||
""" """
|
||||
|
@ -204,6 +208,72 @@ def process_tnc_commands(data):
|
|||
# convert data to json object
|
||||
received_json = json.loads(data)
|
||||
log.debug("[SCK] CMD", command=received_json)
|
||||
|
||||
# ENABLE TNC LISTENING STATE -----------------------------------------------------
|
||||
if received_json["type"] == "set" and received_json["command"] == "listen":
|
||||
try:
|
||||
static.LISTEN = received_json["state"] in ['true', 'True', True, "ON", "on"]
|
||||
command_response("listen", True)
|
||||
|
||||
# if tnc is connected, force disconnect when static.LISTEN == False
|
||||
if not static.LISTEN and static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]:
|
||||
DATA_QUEUE_TRANSMIT.put(["DISCONNECT"])
|
||||
# set early disconnecting state so we can interrupt connection attempts
|
||||
static.ARQ_SESSION_STATE = "disconnecting"
|
||||
command_response("disconnect", True)
|
||||
|
||||
except Exception as err:
|
||||
command_response("listen", False)
|
||||
log.warning(
|
||||
"[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 -----------------------------------------------------
|
||||
if received_json["type"] == "set" and received_json["command"] == "respond_to_cq":
|
||||
try:
|
||||
static.RESPOND_TO_CQ = received_json["state"] in ['true', 'True', True]
|
||||
command_response("respond_to_cq", True)
|
||||
|
||||
except Exception as err:
|
||||
command_response("respond_to_cq", False)
|
||||
log.warning(
|
||||
"[SCK] CQ command execution error", e=err, command=received_json
|
||||
)
|
||||
|
||||
# SET TX AUDIO LEVEL -----------------------------------------------------
|
||||
if (
|
||||
received_json["type"] == "set"
|
||||
|
@ -287,13 +357,22 @@ def process_tnc_commands(data):
|
|||
if not str(dxcallsign).strip():
|
||||
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
|
||||
# then we are forcing a station ssid = 0
|
||||
dxcallsign = helpers.callsign_to_bytes(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)
|
||||
except NoCallsign:
|
||||
command_response("ping", False)
|
||||
|
@ -306,36 +385,79 @@ def process_tnc_commands(data):
|
|||
|
||||
# CONNECT ----------------------------------------------------------
|
||||
if received_json["type"] == "arq" and received_json["command"] == "connect":
|
||||
|
||||
# pause our beacon first
|
||||
static.BEACON_PAUSE = True
|
||||
# send ping frame and wait for ACK
|
||||
|
||||
# check for connection attempts key
|
||||
try:
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
attempts = int(received_json["attempts"])
|
||||
except Exception:
|
||||
# 15 == self.session_connect_max_retries
|
||||
attempts = 15
|
||||
|
||||
# additional step for beeing sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
|
||||
static.DXCALLSIGN = dxcallsign
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.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)
|
||||
|
||||
DATA_QUEUE_TRANSMIT.put(["CONNECT", dxcallsign])
|
||||
command_response("connect", True)
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
mycallsign = static.MYCALLSIGN
|
||||
|
||||
# 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
|
||||
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||
|
||||
if static.ARQ_SESSION_STATE not in ["disconnected", "failed"]:
|
||||
command_response("connect", False)
|
||||
log.warning(
|
||||
"[SCK] Connect command execution error",
|
||||
e=err,
|
||||
e=f"already connected to station:{static.DXCALLSIGN}",
|
||||
command=received_json,
|
||||
)
|
||||
else:
|
||||
|
||||
# finally check again if we are disconnected or failed
|
||||
|
||||
# try connecting
|
||||
try:
|
||||
|
||||
DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts])
|
||||
command_response("connect", True)
|
||||
except Exception as err:
|
||||
command_response("connect", False)
|
||||
log.warning(
|
||||
"[SCK] Connect command execution error",
|
||||
e=err,
|
||||
command=received_json,
|
||||
)
|
||||
# allow beacon transmission again
|
||||
static.BEACON_PAUSE = False
|
||||
|
||||
# allow beacon transmission again
|
||||
static.BEACON_PAUSE = False
|
||||
|
||||
# DISCONNECT ----------------------------------------------------------
|
||||
if received_json["type"] == "arq" and received_json["command"] == "disconnect":
|
||||
# send ping frame and wait for ACK
|
||||
try:
|
||||
DATA_QUEUE_TRANSMIT.put(["DISCONNECT"])
|
||||
command_response("disconnect", True)
|
||||
if static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]:
|
||||
DATA_QUEUE_TRANSMIT.put(["DISCONNECT"])
|
||||
|
||||
# set early disconnecting state so we can interrupt connection attempts
|
||||
static.ARQ_SESSION_STATE = "disconnecting"
|
||||
command_response("disconnect", True)
|
||||
else:
|
||||
command_response("disconnect", False)
|
||||
log.warning(
|
||||
"[SCK] Disconnect command not possible",
|
||||
state=static.ARQ_SESSION_STATE,
|
||||
command=received_json,
|
||||
)
|
||||
except Exception as err:
|
||||
command_response("disconnect", False)
|
||||
log.warning(
|
||||
|
@ -347,6 +469,7 @@ def process_tnc_commands(data):
|
|||
# TRANSMIT RAW DATA -------------------------------------------
|
||||
if received_json["type"] == "arq" and received_json["command"] == "send_raw":
|
||||
static.BEACON_PAUSE = True
|
||||
|
||||
try:
|
||||
if not static.ARQ_SESSION:
|
||||
dxcallsign = received_json["parameter"][0]["dxcallsign"]
|
||||
|
@ -369,9 +492,20 @@ def process_tnc_commands(data):
|
|||
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||
try:
|
||||
mycallsign = received_json["parameter"][0]["mycallsign"]
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||
|
||||
except Exception:
|
||||
mycallsign = static.MYCALLSIGN
|
||||
|
||||
# check for connection attempts key
|
||||
try:
|
||||
attempts = int(received_json["parameter"][0]["attempts"])
|
||||
|
||||
except Exception:
|
||||
# 15 == self.session_connect_max_retries
|
||||
attempts = 15
|
||||
|
||||
# check if transmission uuid provided else set no-uuid
|
||||
try:
|
||||
arq_uuid = received_json["uuid"]
|
||||
|
@ -384,7 +518,7 @@ def process_tnc_commands(data):
|
|||
binarydata = base64.b64decode(base64data)
|
||||
|
||||
DATA_QUEUE_TRANSMIT.put(
|
||||
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign]
|
||||
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, dxcallsign, attempts]
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
|
@ -460,6 +594,32 @@ def process_tnc_commands(data):
|
|||
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
|
||||
except Exception as err:
|
||||
log.error("[SCK] JSON decoding error", e=err)
|
||||
|
@ -478,7 +638,7 @@ def send_tnc_state():
|
|||
"arq_state": str(static.ARQ_STATE),
|
||||
"arq_session": str(static.ARQ_SESSION),
|
||||
"arq_session_state": str(static.ARQ_SESSION_STATE),
|
||||
"audio_rms": str(static.AUDIO_RMS),
|
||||
"audio_dbfs": str(static.AUDIO_DBFS),
|
||||
"snr": str(static.SNR),
|
||||
"frequency": str(static.HAMLIB_FREQUENCY),
|
||||
"speed_level": str(static.ARQ_SPEED_LEVEL),
|
||||
|
@ -491,14 +651,20 @@ def send_tnc_state():
|
|||
"rx_msg_buffer_length": str(len(static.RX_MSG_BUFFER)),
|
||||
"arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE),
|
||||
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
|
||||
"arq_seconds_until_finish": str(static.ARQ_SECONDS_UNTIL_FINISH),
|
||||
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
|
||||
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
|
||||
"speed_list": static.SPEED_LIST,
|
||||
"total_bytes": str(static.TOTAL_BYTES),
|
||||
"beacon_state": str(static.BEACON_STATE),
|
||||
"stations": [],
|
||||
"mycallsign": str(static.MYCALLSIGN, encoding),
|
||||
"mygrid": str(static.MYGRID, encoding),
|
||||
"dxcallsign": str(static.DXCALLSIGN, encoding),
|
||||
"dxgrid": str(static.DXGRID, encoding),
|
||||
"hamlib_status": static.HAMLIB_STATUS,
|
||||
"listen": str(static.LISTEN),
|
||||
"audio_recording": str(static.AUDIO_RECORD),
|
||||
}
|
||||
|
||||
# add heard stations to heard stations object
|
||||
|
@ -514,7 +680,6 @@ def send_tnc_state():
|
|||
"frequency": heard[6],
|
||||
}
|
||||
)
|
||||
|
||||
return json.dumps(output)
|
||||
|
||||
|
||||
|
@ -608,6 +773,17 @@ def process_daemon_commands(data):
|
|||
tx_audio_level = str(received_json["parameter"][0]["tx_audio_level"])
|
||||
respond_to_cq = str(received_json["parameter"][0]["respond_to_cq"])
|
||||
rx_buffer_size = str(received_json["parameter"][0]["rx_buffer_size"])
|
||||
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
|
||||
for item in received_json["parameter"][0]:
|
||||
|
@ -643,6 +819,8 @@ def process_daemon_commands(data):
|
|||
tx_audio_level,
|
||||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
ssid_list,
|
||||
]
|
||||
)
|
||||
command_response("start_tnc", True)
|
||||
|
@ -738,3 +916,4 @@ def command_response(command, status):
|
|||
jsondata = {"command_response": command, "status": s_status}
|
||||
data_out = json.dumps(jsondata)
|
||||
SOCKET_QUEUE.put(data_out)
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@ Not nice, suggestions are appreciated :-)
|
|||
import subprocess
|
||||
from enum import Enum
|
||||
|
||||
VERSION = "0.5.0-alpha"
|
||||
VERSION = "0.6.11-alpha.4"
|
||||
|
||||
ENABLE_EXPLORER = False
|
||||
|
||||
|
||||
# DAEMON
|
||||
DAEMONPORT: int = 3001
|
||||
|
@ -22,7 +25,7 @@ TNCPROCESS: subprocess.Popen
|
|||
MYCALLSIGN: bytes = b"AA0AA"
|
||||
MYCALLSIGN_CRC: bytes = b"A"
|
||||
|
||||
DXCALLSIGN: bytes = b"AA0AA"
|
||||
DXCALLSIGN: bytes = b"ZZ9YY"
|
||||
DXCALLSIGN_CRC: bytes = b"A"
|
||||
|
||||
MYGRID: bytes = b""
|
||||
|
@ -40,7 +43,7 @@ SOCKET_TIMEOUT: int = 1 # seconds
|
|||
# ---------------------------------
|
||||
SERIAL_DEVICES: list = []
|
||||
# ---------------------------------
|
||||
|
||||
LISTEN: bool = True
|
||||
PTT_STATE: bool = False
|
||||
TRANSMITTING: bool = False
|
||||
|
||||
|
@ -57,6 +60,7 @@ HAMLIB_RADIOCONTROL: str = "direct"
|
|||
HAMLIB_RIGCTLD_IP: str = "127.0.0.1"
|
||||
HAMLIB_RIGCTLD_PORT: str = "4532"
|
||||
|
||||
HAMLIB_STATUS: str = "unknown/disconnected"
|
||||
HAMLIB_FREQUENCY: int = 0
|
||||
HAMLIB_MODE: str = ""
|
||||
HAMLIB_BANDWIDTH: int = 0
|
||||
|
@ -69,6 +73,7 @@ SCATTER: list = []
|
|||
ENABLE_SCATTER: bool = False
|
||||
ENABLE_FSK: 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
|
||||
|
@ -77,25 +82,31 @@ AUDIO_INPUT_DEVICES: list = []
|
|||
AUDIO_OUTPUT_DEVICES: list = []
|
||||
AUDIO_INPUT_DEVICE: int = -2
|
||||
AUDIO_OUTPUT_DEVICE: int = -2
|
||||
AUDIO_RECORD: bool = False
|
||||
AUDIO_RECORD_FILE = ''
|
||||
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
||||
|
||||
AUDIO_RMS: int = 0
|
||||
AUDIO_DBFS: int = 0
|
||||
FFT: list = [0]
|
||||
ENABLE_FFT: bool = False
|
||||
ENABLE_FFT: bool = True
|
||||
CHANNEL_BUSY: bool = False
|
||||
|
||||
# ARQ PROTOCOL VERSION
|
||||
ARQ_PROTOCOL_VERSION: int = 2
|
||||
ARQ_PROTOCOL_VERSION: int = 5
|
||||
|
||||
# ARQ statistics
|
||||
SPEED_LIST: list = []
|
||||
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
||||
ARQ_BYTES_PER_MINUTE: int = 0
|
||||
ARQ_BITS_PER_SECOND_BURST: int = 0
|
||||
ARQ_BITS_PER_SECOND: int = 0
|
||||
ARQ_COMPRESSION_FACTOR: int = 0
|
||||
ARQ_TRANSMISSION_PERCENT: int = 0
|
||||
ARQ_SECONDS_UNTIL_FINISH: int = 0
|
||||
ARQ_SPEED_LEVEL: 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'
|
||||
TNC_STATE: str = "IDLE"
|
||||
|
@ -149,4 +160,5 @@ class FRAME_TYPE(Enum):
|
|||
ARQ_DC_OPEN_ACK_N = 228
|
||||
ARQ_STOP = 249
|
||||
BEACON = 250
|
||||
IDENT = 254
|
||||
TEST_FRAME = 255
|
||||
|
|
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