mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Compare commits
275 commits
ec7519400a
...
f7bcdad47f
Author | SHA1 | Date | |
---|---|---|---|
|
f7bcdad47f | ||
|
1b90d38ebf | ||
|
5a3f367a33 | ||
|
6ae542230f | ||
|
f18604a38b | ||
|
caaf613446 | ||
|
9b13efacba | ||
|
4ea78356b6 | ||
|
545aaf031f | ||
|
3086a42da9 | ||
|
6b853511f4 | ||
|
9a772e28c2 | ||
|
eaca7c168e | ||
|
4e83d49144 | ||
|
6e863ddd22 | ||
|
19a473c577 | ||
|
7f7061dae3 | ||
|
432d091c97 | ||
|
8e68aef7f9 | ||
|
0635d35443 | ||
|
1686f554b1 | ||
|
679da34ed7 | ||
|
caa0bb2aac | ||
|
44bf16f15e | ||
|
7794f1afd2 | ||
|
7a32e464f4 | ||
|
6dfc85e983 | ||
|
3d8fe47270 | ||
|
eb3c559ce2 | ||
|
d4a821ac39 | ||
|
47dd3fc23a | ||
|
0e465d8bbb | ||
|
3044759569 | ||
|
2a8d1fd471 | ||
|
1d14f51854 | ||
|
da6dd1f045 | ||
|
21c98a8366 | ||
|
8e9b9c9199 | ||
|
059d23fa31 | ||
|
784aa9cf82 | ||
|
440fda917a | ||
|
7326f871e5 | ||
|
4687dd36e7 | ||
|
93d238d4e1 | ||
|
4c9ee09e86 | ||
|
1bd6537d64 | ||
|
97143f25f6 | ||
|
72d412891e | ||
|
94b4889478 | ||
|
1de42f6ff1 | ||
|
310479fef4 | ||
|
3c0484cf46 | ||
|
0ee9671a7d | ||
|
1c7d9d8d0c | ||
|
81d434bfd1 | ||
|
6dcfe17c5f | ||
|
4904c5e7df | ||
|
32b181b3b0 | ||
|
da1309a335 | ||
|
6ab2f0ce54 | ||
|
4e46577a57 | ||
|
ab96c8bc51 | ||
|
4988a92f52 | ||
|
44395c32f4 | ||
|
034577144b | ||
|
9497d323c8 | ||
|
41a45a9cd9 | ||
|
4a76e5fc1e | ||
|
3727c59584 | ||
|
aba556a996 | ||
|
cc61f6a602 | ||
|
8c63259c26 | ||
|
11826c492d | ||
|
796294f03e | ||
|
5c50ca4803 | ||
|
d54f3dcb31 | ||
|
53697af6e2 | ||
|
ee4528bf79 | ||
|
ac1a82fc5e | ||
|
93d61b0f25 | ||
|
252fe73d68 | ||
|
b1f4dadcc6 | ||
|
df52828207 | ||
|
7ba1dbadb2 | ||
|
5d8b8a4bde | ||
|
03705a041c | ||
|
501ccd9de3 | ||
|
dc4ea2c5dd | ||
|
250943f63f | ||
|
4264f7d9bb | ||
|
bdc407aff9 | ||
|
7831cdaa16 | ||
|
4c53cdc79b | ||
|
bbb08ed7af | ||
|
756101ebe1 | ||
|
a43d90f7d8 | ||
|
28e8aaa595 | ||
|
36f80a5b0a | ||
|
5c19d6f90e | ||
|
d2dc5c98d4 | ||
|
1bd1781dd5 | ||
|
aa6f787630 | ||
|
2cb7ee7893 | ||
|
4ddfe52db9 | ||
|
ab44e18e3e | ||
|
e05577b492 | ||
|
5be2a88fd0 | ||
|
a3f9dd6f45 | ||
|
9c57cc9eb7 | ||
|
0d13b638ad | ||
|
71051fd3fa | ||
|
4d2d0b93ac | ||
|
3b8236826c | ||
|
00d007b9b7 | ||
|
2392ff3b53 | ||
|
17977b5281 | ||
|
8717629b91 | ||
|
47e177f399 | ||
|
e30f09f4de | ||
|
586c02f73b | ||
|
751e43eddb | ||
|
37267ac679 | ||
|
f2e5a11348 | ||
|
efd92cdd72 | ||
|
2ba8b77ea7 | ||
|
4cce8aec5c | ||
|
58342b975f | ||
|
c6d99f8866 | ||
|
c8eb9bbf92 | ||
|
c5a9229207 | ||
|
b637b917bc | ||
|
3dc06510c1 | ||
|
76522db082 | ||
|
d6df3007db | ||
|
5a0a766aa0 | ||
|
eaa16ed50b | ||
|
281731d890 | ||
|
d572772df3 | ||
|
9dad094e47 | ||
|
cb78ed984a | ||
|
fd402d9bc2 | ||
|
9a4401082c | ||
|
c78fff4db1 | ||
|
d78fcba4fb | ||
|
7ed43fb3f9 | ||
|
f5a30e33e3 | ||
|
a9139d035c | ||
|
62309d608d | ||
|
4d8b8f7f46 | ||
|
7691ba09ac | ||
|
c0e4f14da0 | ||
|
424384c7ed | ||
|
e2d4b58e30 | ||
|
76f24f2b31 | ||
|
af851d15f3 | ||
|
9ef19b7c51 | ||
|
914e2065b5 | ||
|
f896dd84c5 | ||
|
f12bf7919d | ||
|
dcec4a4d17 | ||
|
a946ca6555 | ||
|
0f984c26b2 | ||
|
10d337b962 | ||
|
855159d150 | ||
|
443d7931b7 | ||
|
33e0c2d497 | ||
|
af7c9cbafb | ||
|
a546f0f387 | ||
|
43eff325d1 | ||
|
eba1cde5b4 | ||
|
d26186e6fd | ||
|
495b988701 | ||
|
f730fbe3d7 | ||
|
6b60fd4275 | ||
|
d8255fbd2f | ||
|
c143c87d64 | ||
|
f979fb3d7b | ||
|
934bb20010 | ||
|
9d26472607 | ||
|
847c3928df | ||
|
f64c4ff0dd | ||
|
1937606526 | ||
|
771afabd7d | ||
|
e069be45ba | ||
|
242b4adc5f | ||
|
12d1477c36 | ||
|
e672218594 | ||
|
9c8237f505 | ||
|
db9c5b60b9 | ||
|
179f4f7265 | ||
|
dfa07f9e77 | ||
|
50bbcfd7ff | ||
|
5d8b4f2d67 | ||
|
819832127e | ||
|
6e3edb5b30 | ||
|
10b925d70b | ||
|
2cdea7eaa9 | ||
|
19bcde01b0 | ||
|
2e12b2ed92 | ||
|
bdeba55f8e | ||
|
11512d76bf | ||
|
d9a7d392ce | ||
|
8c0c13f227 | ||
|
ddf6cb0ee0 | ||
|
3d6bf72633 | ||
|
331102e03d | ||
|
cc75d351ab | ||
|
f2dd278bce | ||
|
af889f9460 | ||
|
dba907dabf | ||
|
75c514f342 | ||
|
362cef9b08 | ||
|
56701347fc | ||
|
51d7c681a6 | ||
|
1b1bb18bb2 | ||
|
4c611731a8 | ||
|
db315bae72 | ||
|
c499a6a524 | ||
|
9dcf0959b4 | ||
|
d0d853d72b | ||
|
85b16c7935 | ||
|
b543c914a0 | ||
|
4c69b748d5 | ||
|
83b3f6a71c | ||
|
12933e96b7 | ||
|
90c4bf9360 | ||
|
29c1d0ad72 | ||
|
c3f198fda9 | ||
|
eeffb8c6e4 | ||
|
9468ceb6fc | ||
|
02c08a71a8 | ||
|
ac62fe4448 | ||
|
d5a0464fd9 | ||
|
a2fd010198 | ||
|
ea1b6cb27c | ||
|
2de80520e1 | ||
|
fa7360f1f3 | ||
|
9941c7ea4d | ||
|
6ea653c5aa | ||
|
8251b35ebb | ||
|
8b2412de96 | ||
|
4b5622647e | ||
|
7cd01e1e74 | ||
|
45a5e60b2b | ||
|
bc72e5f3ba | ||
|
ca955b1ff2 | ||
|
40beee630c | ||
|
37da268547 | ||
|
cf4b3bf9cd | ||
|
564f4c106e | ||
|
ba2e2b8ce9 | ||
|
cd2b0dc133 | ||
|
93f951663e | ||
|
1aa3073aa2 | ||
|
ce19b9c2d9 | ||
|
03d4cce4a7 | ||
|
966198a9c3 | ||
|
913bce2ea5 | ||
|
048b5c85b5 | ||
|
a9c415c59e | ||
|
a68d2c176a | ||
|
53d0383696 | ||
|
9053e2f57f | ||
|
f21069e29d | ||
|
18f670bac7 | ||
|
03f5f8dcce | ||
|
c93cba9bc0 | ||
|
a67d6cb450 | ||
|
5516985f49 | ||
|
7efad9479c | ||
|
b125f36a13 | ||
|
32a01a5859 | ||
|
63d47cb745 | ||
|
b97db66736 | ||
|
19bff8cb4e |
36 changed files with 2481 additions and 704 deletions
118
.github/workflows/build_multiplatform.yml
vendored
118
.github/workflows/build_multiplatform.yml
vendored
|
@ -100,9 +100,6 @@ jobs:
|
|||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
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:
|
||||
|
@ -236,7 +234,7 @@ jobs:
|
|||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Create tnc/dist
|
||||
working-directory: tnc
|
||||
|
@ -268,7 +266,7 @@ jobs:
|
|||
# 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')}}
|
||||
|
@ -282,27 +280,25 @@ jobs:
|
|||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Add MacOS certs
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
|
||||
env:
|
||||
CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }}
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
|
||||
|
||||
# - name: Install Pyaudio Windows
|
||||
# if: ${{startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: tnc/lib/pyaudio/windows
|
||||
# run: |
|
||||
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl
|
||||
- name: Build binaries macOS
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
# now build tnc binaries
|
||||
pyinstaller -y freedata.spec
|
||||
# and to some final cleanup
|
||||
# cp -r -f dist/tnc/* dist/
|
||||
# rm -r dist/tnc
|
||||
|
||||
# - name: Display structure of downloaded files
|
||||
# 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: Build binaries
|
||||
- name: Build binaries Linux and Windows
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
# pyinstaller freedata.spec
|
||||
|
@ -311,8 +307,8 @@ jobs:
|
|||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
|
||||
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
|
||||
|
||||
- name: Copy binaries - Linux and MacOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
- name: Copy binaries - Linux
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
cp -r -f daemon.dist/* dist/tnc
|
||||
|
@ -327,6 +323,8 @@ jobs:
|
|||
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/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
|
||||
|
@ -336,62 +334,43 @@ jobs:
|
|||
with:
|
||||
path: tnc/dist/tnc
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Download Portaudio binaries
|
||||
- name: Download Portaudio binaries Linux macOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
working-directory: tnc
|
||||
run: |
|
||||
if ! test -d "dist/tnc/_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
|
||||
run: |
|
||||
if(Test-Path -Path "dist/_sounddevice_data"){
|
||||
echo "sounddevice folder already exists"
|
||||
} else {
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
|
||||
}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
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 }}'
|
||||
## # directory: ./tnc/dist/tnc
|
||||
## directory: ./tnc/dist/tnc
|
||||
## path: .
|
||||
## # exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
##- name: LIST ALL FILES
|
||||
## run: ls -R
|
||||
|
||||
##- name: Upload TNC artifacts
|
||||
## uses: actions/upload-artifact@v3
|
||||
## with:
|
||||
## name: ${{ matrix.zip_name }}.zip
|
||||
## # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
## path: ./tnc/dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
|
||||
##- name: Release TNC
|
||||
## uses: softprops/action-gh-release@v1
|
||||
## if: startsWith(github.ref, 'refs/tags/v')
|
||||
## with:
|
||||
## files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
|
||||
## #files: ./tnc/dist/${{ matrix.zip_name }}.zip
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
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
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
with:
|
||||
package_root: "./gui/"
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
@ -399,6 +378,7 @@ 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
|
||||
|
|
1
.github/workflows/ctest.yml
vendored
1
.github/workflows/ctest.yml
vendored
|
@ -21,6 +21,7 @@ jobs:
|
|||
- python-version: "3.9"
|
||||
- python-version: "3.10"
|
||||
- python-version: "3.11"
|
||||
- python-version: "3.12-dev"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
23
add-osx-cert.sh
Normal file
23
add-osx-cert.sh
Normal file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
KEY_CHAIN=build.keychain
|
||||
CERTIFICATE_P12=certificate.p12
|
||||
|
||||
# Recreate the certificate from the secure environment variable
|
||||
echo $CERTIFICATE_OSX_APPLICATION | base64 --decode > $CERTIFICATE_P12
|
||||
|
||||
#create a keychain
|
||||
security create-keychain -p actions $KEY_CHAIN
|
||||
|
||||
# Make the keychain the default so identities are found
|
||||
security default-keychain -s $KEY_CHAIN
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p actions $KEY_CHAIN
|
||||
|
||||
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign;
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
|
||||
|
||||
# remove certs
|
||||
rm -fr *.p12
|
16
gui/build/entitlements.plist
Normal file
16
gui/build/entitlements.plist
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -92,7 +92,8 @@ const configDefaultSettings = '{\
|
|||
"tuning_range_fmax" : "50.0",\
|
||||
"respond_to_cq" : "True",\
|
||||
"rx_buffer_size" : "16", \
|
||||
"enable_explorer" : "False" \
|
||||
"enable_explorer" : "False", \
|
||||
"wftheme": 2 \
|
||||
}';
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"version": "0.6.7-alpha.1",
|
||||
"version": "0.6.12-alpha.6",
|
||||
"description": "FreeDATA ",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
@ -28,50 +28,54 @@
|
|||
},
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"electron": "^20.1.3",
|
||||
"electron-builder": "^23.3.3"
|
||||
"electron-builder": "^23.3.3",
|
||||
"electron-builder-notarize": "^1.5.0"
|
||||
},
|
||||
"build": {
|
||||
"productName": "FreeDATA",
|
||||
"appId": "app.freedata",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"npmRebuild": "false",
|
||||
"directories": {
|
||||
"buildResources": "build",
|
||||
"output": "dist"
|
||||
},
|
||||
"dmg": {
|
||||
"mac": {
|
||||
"target": [
|
||||
"default"
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
|
@ -95,7 +99,8 @@
|
|||
"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,25 +61,51 @@ try{
|
|||
}
|
||||
|
||||
PouchDB.plugin(require('pouchdb-find'));
|
||||
var db = new PouchDB(chatDB);
|
||||
var remoteDB = new PouchDB('http://192.168.178.79:5984/chatDB')
|
||||
//PouchDB.plugin(require('pouchdb-replication'));
|
||||
|
||||
db.sync(remoteDB, {
|
||||
var db = new PouchDB(chatDB);
|
||||
|
||||
/*
|
||||
// REMOTE SYNC ATTEMPTS
|
||||
|
||||
|
||||
var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB')
|
||||
|
||||
// we need express packages for running pouchdb sync "express-pouchdb"
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
//app.use('/chatDB', require('express-pouchdb')(PouchDB));
|
||||
//app.listen(5984);
|
||||
|
||||
app.use('/chatDB', require('pouchdb-express-router')(PouchDB));
|
||||
app.listen(5984);
|
||||
|
||||
|
||||
|
||||
db.sync('http://172.20.10.4:5984/jojo', {
|
||||
//var sync = PouchDB.sync('chatDB', 'http://172.20.10.4:5984/chatDB', {
|
||||
live: true,
|
||||
retry: true
|
||||
retry: false
|
||||
}).on('change', function (change) {
|
||||
// yo, something changed!
|
||||
console.log(change)
|
||||
}).on('paused', function (info) {
|
||||
}).on('paused', function (err) {
|
||||
// replication was paused, usually because of a lost connection
|
||||
console.log(info)
|
||||
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(error)
|
||||
console.log(err)
|
||||
}).on('denied', function (err) {
|
||||
// a document failed to replicate (e.g. due to permissions)
|
||||
console.log(err)
|
||||
}).on('complete', function (info) {
|
||||
// handle complete;
|
||||
console.log(info)
|
||||
});
|
||||
*/
|
||||
|
||||
var dxcallsigns = new Set();
|
||||
db.createIndex({
|
||||
|
@ -297,7 +323,9 @@ db.post({
|
|||
}
|
||||
var timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
var data_with_attachment = chatmessage + split_char + filename + split_char + filetype + split_char + file + split_char + timestamp;
|
||||
var file_checksum = crc32(file).toString(16).toUpperCase();
|
||||
console.log(file_checksum)
|
||||
var data_with_attachment = timestamp + split_char + chatmessage + split_char + filename + split_char + filetype + split_char + file;
|
||||
|
||||
document.getElementById('selectFilesButton').innerHTML = ``;
|
||||
var uuid = uuidv4();
|
||||
|
@ -308,7 +336,7 @@ db.post({
|
|||
mode: 255,
|
||||
frames: 1,
|
||||
data: data_with_attachment,
|
||||
checksum: '123',
|
||||
checksum: file_checksum,
|
||||
uuid: uuid
|
||||
};
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
|
@ -318,7 +346,7 @@ db.post({
|
|||
dxcallsign: dxcallsign,
|
||||
dxgrid: 'null',
|
||||
msg: chatmessage,
|
||||
checksum: 'null',
|
||||
checksum: file_checksum,
|
||||
type: "transmit",
|
||||
status: 'transmit',
|
||||
uuid: uuid,
|
||||
|
@ -404,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;
|
||||
|
@ -421,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;
|
||||
|
@ -451,20 +476,20 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
|
||||
console.log(splitted_data)
|
||||
|
||||
obj.timestamp = splitted_data[8];
|
||||
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);
|
||||
|
@ -827,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 + split_char + doc.timestamp;
|
||||
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);
|
||||
|
@ -935,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,
|
||||
|
@ -964,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;
|
||||
};
|
|
@ -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');
|
||||
|
@ -37,6 +37,87 @@ var dbfs_level_raw = 0
|
|||
// 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', () => {
|
||||
|
||||
|
@ -96,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)
|
||||
|
@ -208,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
|
||||
|
@ -291,10 +413,13 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
|
|||
// Create spectrum object on canvas with ID "waterfall"
|
||||
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
|
||||
|
@ -690,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
|
||||
|
@ -798,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));
|
||||
|
@ -965,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));
|
||||
|
@ -996,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", () => {
|
||||
|
||||
|
@ -1314,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,
|
||||
|
@ -1389,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)'
|
||||
}],
|
||||
};
|
||||
|
@ -1408,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";
|
||||
|
@ -1436,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";
|
||||
|
@ -1532,7 +1808,10 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
@ -1555,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
|
||||
|
||||
|
@ -1574,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";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2070,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);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
|
57
gui/sock.js
57
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
|
||||
|
@ -195,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'],
|
||||
|
@ -220,12 +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);
|
||||
|
@ -514,20 +521,25 @@ 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 + '", "attempts": "15"}]}'
|
||||
socketLog.info(command)
|
||||
|
@ -583,7 +595,24 @@ exports.sendTestFrame = function() {
|
|||
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();
|
||||
|
@ -601,4 +630,12 @@ ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
|||
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};
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
<button class="btn btn-sm btn-danger" id="stop_transmission_connection" type="button"> <i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i> STOP </button>
|
||||
</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!">
|
||||
<!--
|
||||
|
||||
|
@ -731,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">
|
||||
|
@ -739,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>
|
||||
|
@ -770,6 +769,8 @@
|
|||
<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;">
|
||||
|
@ -837,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>
|
||||
|
@ -1066,28 +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-2" role="group">
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button class="btn btn-sm btn-secondary" id="rigctld_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"> <i class="bi bi-usb-symbol" style="font-size: 0.8rem; color: white;"></i> </button>
|
||||
</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">
|
||||
|
@ -1100,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>
|
||||
|
@ -1107,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>
|
||||
|
@ -1172,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>
|
||||
|
@ -1295,4 +1326,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -8,6 +8,7 @@ body {
|
|||
/*Progress bars with centered text*/
|
||||
.progress {
|
||||
position: relative;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.progress span {
|
||||
|
|
|
@ -68,7 +68,8 @@ 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;
|
||||
|
||||
|
@ -88,40 +89,34 @@ Spectrum.prototype.drawSpectrum = function(bins) {
|
|||
this.ctx_wf.stroke()
|
||||
*/
|
||||
|
||||
// 586Hz LINES
|
||||
var bandwidth = 568;
|
||||
var linePositionLow = 150 - ((bandwidth/2)/10);
|
||||
var linePositionHigh = 150 + ((bandwidth/2)/10);
|
||||
// 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()
|
||||
|
||||
// 1700Hz LINES
|
||||
var bandwidth = 1700;
|
||||
var linePositionLow = 150 - ((bandwidth/2)/10);
|
||||
var linePositionHigh = 150 + ((bandwidth/2)/10);
|
||||
this.ctx_wf.beginPath();
|
||||
this.ctx_wf.moveTo(linePositionLow,0);
|
||||
this.ctx_wf.lineTo(linePositionLow, height);
|
||||
this.ctx_wf.moveTo(linePositionHigh,0);
|
||||
this.ctx_wf.lineTo(linePositionHigh, height);
|
||||
this.ctx_wf.lineWidth = 1;
|
||||
this.ctx_wf.strokeStyle = '#C3C3C3';
|
||||
this.ctx_wf.stroke()
|
||||
|
||||
|
||||
// ---- END OF MODIFICATION ------
|
||||
// ---- END OF MODIFICATION ------
|
||||
|
||||
|
||||
// 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) {
|
||||
|
@ -174,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() {
|
||||
|
@ -242,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();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ sounddevice
|
|||
structlog
|
||||
ujson
|
||||
requests
|
||||
chardet
|
||||
colorama
|
||||
ordered-set
|
||||
nuitka
|
||||
|
@ -17,9 +18,9 @@ autopep8
|
|||
black
|
||||
isort
|
||||
pycodestyle
|
||||
pyinstaller
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-cover
|
||||
pytest-coverage
|
||||
pytest-rerunfailures
|
||||
pick
|
|
@ -93,7 +93,7 @@ def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
|
|||
return t_create_frame(223, mycall, dxcall)
|
||||
|
||||
|
||||
def t_create_session_close(session_id: bytes) -> bytearray:
|
||||
def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray:
|
||||
"""
|
||||
Generate the session_close frame.
|
||||
|
||||
|
@ -102,10 +102,16 @@ def t_create_session_close(session_id: bytes) -> bytearray:
|
|||
: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
|
||||
|
||||
|
@ -183,7 +189,7 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
|
|||
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)
|
||||
close_frame = t_create_session_close(wrong_session, dxcall)
|
||||
print_frame(close_frame)
|
||||
|
||||
# assert (
|
||||
|
@ -254,7 +260,10 @@ def t_valid_disconnect(mycall: str, dxcall: str):
|
|||
# Create packet to be 'received' by this station.
|
||||
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
|
||||
open_session = create_frame[1:2]
|
||||
close_frame = t_create_session_close(open_session)
|
||||
print(dxcall)
|
||||
print("#####################################################")
|
||||
close_frame = t_create_session_close(open_session, mycall)
|
||||
print(close_frame[2:5])
|
||||
print_frame(close_frame)
|
||||
tnc.received_session_close(close_frame)
|
||||
|
||||
|
|
|
@ -296,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!")
|
||||
|
|
|
@ -287,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
|
||||
|
@ -302,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!")
|
||||
|
|
|
@ -60,17 +60,20 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
|
|||
|
||||
|
||||
# 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("."))
|
||||
#if hasattr(sys, "_MEIPASS"):
|
||||
# sys.path.append(getattr(sys, "_MEIPASS"))
|
||||
#else:
|
||||
sys.path.append(os.path.abspath("."))
|
||||
|
||||
log.info("[C2 ] Searching for libcodec2...")
|
||||
if sys.platform == "linux":
|
||||
files = glob.glob(r"**/*libcodec2*", recursive=True)
|
||||
files.append("libcodec2.so")
|
||||
elif sys.platform == "darwin":
|
||||
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
files = glob.glob(getattr(sys, "_MEIPASS") + '/**/*libcodec2*', recursive=True)
|
||||
else:
|
||||
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
|
||||
elif sys.platform in ["win32", "win64"]:
|
||||
files = glob.glob(r"**\*libcodec2*.dll", recursive=True)
|
||||
else:
|
||||
|
|
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,
|
||||
|
|
|
@ -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])
|
||||
|
@ -251,17 +252,40 @@ class DAEMON:
|
|||
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)
|
||||
import os, sys
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
|
||||
# Try running tnc from binary, else run from source
|
||||
# This helps running the tnc in a developer environment
|
||||
try:
|
||||
command = []
|
||||
if sys.platform in ["linux", "darwin"]:
|
||||
command.append("./freedata-tnc")
|
||||
elif sys.platform in ["win32", "win64"]:
|
||||
command.append("freedata-tnc.exe")
|
||||
|
||||
if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]:
|
||||
# If the application is run as a bundle, the PyInstaller bootloader
|
||||
# extends the sys module by a flag frozen=True and sets the app
|
||||
# path into variable _MEIPASS'.
|
||||
application_path = sys._MEIPASS
|
||||
command.append(application_path + '/freedata-tnc')
|
||||
|
||||
else:
|
||||
|
||||
if sys.platform in ["linux", "darwin"]:
|
||||
command.append("./freedata-tnc")
|
||||
elif sys.platform in ["win32", "win64"]:
|
||||
command.append("freedata-tnc.exe")
|
||||
|
||||
command += options
|
||||
proc = subprocess.Popen(command)
|
||||
|
@ -272,6 +296,7 @@ class DAEMON:
|
|||
except FileNotFoundError as err1:
|
||||
self.log.info("[DMN] worker: ", e=err1)
|
||||
command = []
|
||||
|
||||
if sys.platform in ["linux", "darwin"]:
|
||||
command.append("python3")
|
||||
elif sys.platform in ["win32", "win64"]:
|
||||
|
@ -279,6 +304,7 @@ class DAEMON:
|
|||
|
||||
command.append("main.py")
|
||||
command += options
|
||||
print(command)
|
||||
proc = subprocess.Popen(command)
|
||||
atexit.register(proc.kill)
|
||||
|
||||
|
@ -407,7 +433,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)
|
||||
|
@ -434,4 +460,4 @@ if __name__ == "__main__":
|
|||
version=static.VERSION,
|
||||
)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
threading.Event().wait(1)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,15 +28,11 @@ class explorer():
|
|||
def interval(self):
|
||||
while True:
|
||||
self.push()
|
||||
time.sleep(self.publish_interval)
|
||||
threading.Event().wait(self.publish_interval)
|
||||
|
||||
def push(self):
|
||||
|
||||
|
||||
if static.HAMLIB_FREQUENCY is not None:
|
||||
frequency = static.HAMLIB_FREQUENCY
|
||||
else:
|
||||
frequency = 0
|
||||
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")
|
||||
|
@ -47,7 +43,21 @@ class explorer():
|
|||
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon}
|
||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []}
|
||||
|
||||
for i in static.HEARD_STATIONS:
|
||||
try:
|
||||
callsign = str(i[0], "UTF-8")
|
||||
grid = str(i[1], "UTF-8")
|
||||
timestamp = i[2]
|
||||
try:
|
||||
snr = i[4].split("/")[1]
|
||||
except AttributeError:
|
||||
snr = str(i[4])
|
||||
station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr, "timestamp": timestamp})
|
||||
except Exception as e:
|
||||
log.debug("[EXPLORER] not publishing station", e=e)
|
||||
|
||||
station_data = json.dumps(station_data)
|
||||
try:
|
||||
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
||||
|
|
|
@ -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,11 +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")
|
||||
|
||||
|
@ -25,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
|
||||
|
||||
|
||||
|
@ -131,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:
|
||||
|
@ -315,7 +316,7 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
|
|||
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid)
|
||||
return [True, bytes(call_with_ssid)]
|
||||
|
||||
return [False, ""]
|
||||
return [False, b'']
|
||||
|
||||
|
||||
def check_session_id(id: bytes, id_to_check: bytes):
|
||||
|
@ -330,6 +331,8 @@ def check_session_id(id: bytes, id_to_check: bytes):
|
|||
True
|
||||
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
|
||||
|
||||
|
@ -391,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):
|
||||
|
|
|
@ -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.
|
||||
|
|
150
tnc/main.py
150
tnc/main.py
|
@ -31,6 +31,7 @@ import modem
|
|||
import static
|
||||
import structlog
|
||||
import explorer
|
||||
import json
|
||||
|
||||
log = structlog.get_logger("main")
|
||||
|
||||
|
@ -57,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",
|
||||
|
@ -90,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",
|
||||
|
@ -261,48 +278,13 @@ if __name__ == "__main__":
|
|||
help="Enable sending tnc data to https://explorer.freedata.app",
|
||||
)
|
||||
ARGS = PARSER.parse_args()
|
||||
if ARGS.configfile:
|
||||
# init config
|
||||
config = config.CONFIG().read_config()
|
||||
|
||||
# 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)
|
||||
# set save to folder state for allowing downloading files to local file system
|
||||
static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder
|
||||
|
||||
static.SSID_LIST = config['STATION']['ssid']
|
||||
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
|
||||
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
|
||||
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
|
||||
static.PORT = int(config['NETWORK']['tncport'])
|
||||
# TODO: disabled because we don't need these settings anymore.
|
||||
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
|
||||
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
|
||||
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
|
||||
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
|
||||
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
|
||||
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
|
||||
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
|
||||
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
|
||||
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
|
||||
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
|
||||
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
|
||||
static.ENABLE_SCATTER = config['TNC']['scatter']
|
||||
static.ENABLE_FFT = config['TNC']['fft']
|
||||
static.ENABLE_FSK = False
|
||||
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband']
|
||||
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
|
||||
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
|
||||
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
|
||||
static.RESPOND_TO_CQ = config['TNC']['qrv']
|
||||
static.RX_BUFFER_SIZE = config['TNC']['rxbuffersize']
|
||||
static.ENABLE_EXPLORER = config['TNC']['explorer']
|
||||
|
||||
|
||||
else:
|
||||
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
|
||||
|
@ -311,11 +293,24 @@ if __name__ == "__main__":
|
|||
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.MYGRID = bytes(ARGS.mygrid, "utf-8")
|
||||
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
|
||||
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
|
||||
|
||||
# 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
|
||||
|
@ -341,6 +336,67 @@ if __name__ == "__main__":
|
|||
except Exception as e:
|
||||
log.error("[DMN] Error reading config file", exception=e)
|
||||
|
||||
else:
|
||||
configfile = ARGS.configfile
|
||||
# init config
|
||||
config = config.CONFIG(configfile).read_config()
|
||||
try:
|
||||
# additional step for being sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
mycallsign = bytes(config['STATION']['mycall'], "utf-8")
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
|
||||
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
|
||||
|
||||
#json.loads = for converting str list to list
|
||||
static.SSID_LIST = json.loads(config['STATION']['ssid_list'])
|
||||
|
||||
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
|
||||
# check if we have an int or str as device name
|
||||
try:
|
||||
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
|
||||
except ValueError:
|
||||
static.AUDIO_INPUT_DEVICE = config['AUDIO']['rx']
|
||||
try:
|
||||
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
|
||||
except ValueError:
|
||||
static.AUDIO_OUTPUT_DEVICE = config['AUDIO']['tx']
|
||||
|
||||
static.PORT = int(config['NETWORK']['tncport'])
|
||||
# TODO: disabled because we don't need these settings anymore.
|
||||
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
|
||||
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
|
||||
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
|
||||
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
|
||||
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
|
||||
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
|
||||
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
|
||||
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
|
||||
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
|
||||
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
|
||||
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
|
||||
static.ENABLE_SCATTER = config['TNC']['scatter'] in ["True", "true", True]
|
||||
static.ENABLE_FFT = config['TNC']['fft'] in ["True", "true", True]
|
||||
static.ENABLE_FSK = False
|
||||
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband'] in ["True", "true", True]
|
||||
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
|
||||
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
|
||||
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
|
||||
static.RESPOND_TO_CQ = config['TNC']['qrv'] in ["True", "true", True]
|
||||
static.RX_BUFFER_SIZE = int(config['TNC']['rxbuffersize'])
|
||||
static.ENABLE_EXPLORER = config['TNC']['explorer'] in ["True", "true", True]
|
||||
|
||||
except KeyError as e:
|
||||
log.warning("[CFG] Error reading config file near", key=str(e))
|
||||
except Exception as e:
|
||||
log.warning("[CFG] Error", e=e)
|
||||
|
||||
# make sure the own ssid is always part of the ssid list
|
||||
my_ssid = int(static.MYCALLSIGN.split(b'-')[1])
|
||||
if my_ssid not in static.SSID_LIST:
|
||||
static.SSID_LIST.append(my_ssid)
|
||||
|
||||
# we need to wait until we got all parameters from argparse first before we can load the other modules
|
||||
import sock
|
||||
|
||||
|
@ -399,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)
|
||||
|
|
272
tnc/modem.py
272
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 = ""
|
||||
|
@ -38,6 +40,13 @@ 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"""
|
||||
|
@ -60,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
|
||||
|
@ -141,6 +151,10 @@ class RF:
|
|||
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:
|
||||
|
@ -266,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
|
||||
|
@ -284,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:
|
||||
|
@ -307,7 +328,7 @@ class RF:
|
|||
# (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)
|
||||
|
@ -315,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))
|
||||
|
||||
|
@ -348,30 +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.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)
|
||||
# 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
|
||||
|
||||
|
@ -395,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)
|
||||
|
@ -431,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
|
||||
)
|
||||
|
@ -499,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
|
||||
|
@ -518,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)
|
||||
|
||||
|
@ -534,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()
|
||||
|
@ -550,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
|
||||
|
@ -565,6 +619,10 @@ 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
|
||||
"""
|
||||
|
@ -577,17 +635,39 @@ class RF:
|
|||
nbytes = codec2.api.freedv_rawdatarx(
|
||||
freedv, bytes_out, audiobuffer.buffer.ctypes
|
||||
)
|
||||
# get current modem states and write to list
|
||||
# 1 trial
|
||||
# 2 sync
|
||||
# 3 trial sync
|
||||
# 6 decoded
|
||||
# 10 error decoding == NACK
|
||||
rx_status = 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"
|
||||
"[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",
|
||||
|
@ -675,6 +755,8 @@ class RF:
|
|||
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:
|
||||
|
@ -685,6 +767,8 @@ class RF:
|
|||
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:
|
||||
|
@ -695,6 +779,8 @@ class RF:
|
|||
self.dat0_datac1_freedv,
|
||||
self.dat0_datac1_bytes_out,
|
||||
self.dat0_datac1_bytes_per_frame,
|
||||
DAT0_DATAC1_STATE,
|
||||
"dat0-datac1"
|
||||
)
|
||||
|
||||
def audio_dat0_datac3(self) -> None:
|
||||
|
@ -705,6 +791,8 @@ class RF:
|
|||
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:
|
||||
|
@ -730,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]
|
||||
)
|
||||
|
@ -783,13 +876,21 @@ class RF:
|
|||
)
|
||||
|
||||
scatterdata = []
|
||||
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)})
|
||||
# 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:
|
||||
|
@ -798,7 +899,6 @@ class RF:
|
|||
# only take every tenth data point
|
||||
static.SCATTER = scatterdata[::10]
|
||||
|
||||
|
||||
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
||||
"""
|
||||
Ask codec2 for data about the received signal and calculate
|
||||
|
@ -832,6 +932,20 @@ class RF:
|
|||
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
|
||||
|
@ -841,7 +955,7 @@ 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()
|
||||
|
@ -859,7 +973,7 @@ class RF:
|
|||
rms_counter = 0
|
||||
|
||||
while True:
|
||||
# time.sleep(0.01)
|
||||
# threading.Event().wait(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
# WE NEED TO OPTIMIZE THIS!
|
||||
|
||||
|
@ -892,12 +1006,22 @@ class RF:
|
|||
# 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.float)
|
||||
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
|
||||
rms = int(np.sqrt(np.max(d ** 2)))
|
||||
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
||||
# 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
|
||||
|
||||
|
@ -919,20 +1043,16 @@ class RF:
|
|||
# 3200Hz = 315
|
||||
|
||||
# define the area, we are detecting busy state
|
||||
if static.LOW_BANDWIDTH_MODE:
|
||||
dfft = dfft[120:176]
|
||||
else:
|
||||
dfft = dfft[65:231]
|
||||
|
||||
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 + 15]) >= 400 and not static.TRANSMITTING:
|
||||
static.CHANNEL_BUSY = True
|
||||
# Limit delay counter to a maximum of 250. The higher this value,
|
||||
# Limit delay counter to a maximum of 200. The higher this value,
|
||||
# the longer we will wait until releasing state
|
||||
channel_busy_delay = min(channel_busy_delay + 10, 250)
|
||||
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)
|
||||
|
@ -1032,3 +1152,19 @@ def set_audio_volume(datalist, volume: float) -> np.int16:
|
|||
# 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()
|
196
tnc/rigctld.py
196
tnc/rigctld.py
|
@ -4,9 +4,11 @@
|
|||
#
|
||||
# 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
|
||||
|
@ -19,9 +21,11 @@ class radio:
|
|||
|
||||
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.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
|
||||
|
@ -31,7 +35,6 @@ class radio:
|
|||
self.frequency = ''
|
||||
self.mode = ''
|
||||
|
||||
|
||||
def open_rig(
|
||||
self,
|
||||
devicename,
|
||||
|
@ -65,8 +68,20 @@ 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(
|
||||
|
@ -74,33 +89,58 @@ class radio:
|
|||
)
|
||||
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] 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.
|
||||
|
||||
|
@ -108,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!",
|
||||
|
@ -118,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!",
|
||||
|
@ -129,27 +192,24 @@ 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.connected else "unknown/disconnected"
|
||||
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")
|
||||
data = data[0].decode("utf-8")
|
||||
if 'RPRT' not in data:
|
||||
self.mode = data
|
||||
try:
|
||||
data = int(data)
|
||||
except ValueError:
|
||||
self.mode = str(data)
|
||||
|
||||
return self.mode
|
||||
except Exception:
|
||||
|
@ -158,12 +218,13 @@ class radio:
|
|||
def get_bandwidth(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = self.send_data_command(b"m", True)
|
||||
data = data.split(b"\n")
|
||||
data = data[1].decode("utf-8")
|
||||
|
||||
if 'RPRT' not in data:
|
||||
self.bandwidth = int(data)
|
||||
if 'RPRT' not in data and data not in ['']:
|
||||
with contextlib.suppress(ValueError):
|
||||
self.bandwidth = int(data)
|
||||
return self.bandwidth
|
||||
except Exception:
|
||||
return self.bandwidth
|
||||
|
@ -171,11 +232,14 @@ class radio:
|
|||
def get_frequency(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"f")
|
||||
data = self.send_data_command(b"f", True)
|
||||
data = data.decode("utf-8")
|
||||
if 'RPRT' not in data:
|
||||
self.frequency = data
|
||||
|
||||
if 'RPRT' not in data and data not in [0, '0', '']:
|
||||
with contextlib.suppress(ValueError):
|
||||
data = int(data)
|
||||
# make sure we have a frequency and not bandwidth
|
||||
if data >= 10000:
|
||||
self.frequency = data
|
||||
return self.frequency
|
||||
except Exception:
|
||||
return self.frequency
|
||||
|
@ -183,7 +247,7 @@ class radio:
|
|||
def get_ptt(self):
|
||||
""" """
|
||||
try:
|
||||
return self.send_command(b"t")
|
||||
return self.send_command(b"t", True)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
@ -198,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,16 @@ class radio:
|
|||
"""
|
||||
return None
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
"""
|
||||
|
||||
Args:
|
||||
mode:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return None
|
||||
def get_status(self):
|
||||
"""
|
||||
|
||||
|
|
137
tnc/sock.py
137
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):
|
||||
""" """
|
||||
|
@ -224,6 +228,40 @@ def process_tnc_commands(data):
|
|||
"[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:
|
||||
|
@ -319,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)
|
||||
|
@ -351,6 +398,15 @@ def process_tnc_commands(data):
|
|||
|
||||
dxcallsign = received_json["dxcallsign"]
|
||||
|
||||
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||
try:
|
||||
mycallsign = received_json["mycallsign"]
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||
|
||||
except Exception:
|
||||
mycallsign = static.MYCALLSIGN
|
||||
|
||||
# additional step for being sure our callsign is correctly
|
||||
# in case we are not getting a station ssid
|
||||
# then we are forcing a station ssid = 0
|
||||
|
@ -370,10 +426,8 @@ def process_tnc_commands(data):
|
|||
|
||||
# try connecting
|
||||
try:
|
||||
static.DXCALLSIGN = dxcallsign
|
||||
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
|
||||
|
||||
DATA_QUEUE_TRANSMIT.put(["CONNECT", dxcallsign, attempts])
|
||||
DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts])
|
||||
command_response("connect", True)
|
||||
except Exception as err:
|
||||
command_response("connect", False)
|
||||
|
@ -385,7 +439,6 @@ def process_tnc_commands(data):
|
|||
# allow beacon transmission again
|
||||
static.BEACON_PAUSE = False
|
||||
|
||||
|
||||
# allow beacon transmission again
|
||||
static.BEACON_PAUSE = False
|
||||
|
||||
|
@ -439,6 +492,9 @@ def process_tnc_commands(data):
|
|||
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||
try:
|
||||
mycallsign = received_json["parameter"][0]["mycallsign"]
|
||||
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||
|
||||
except Exception:
|
||||
mycallsign = static.MYCALLSIGN
|
||||
|
||||
|
@ -462,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, attempts]
|
||||
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, dxcallsign, attempts]
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
|
@ -538,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)
|
||||
|
@ -569,16 +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
|
||||
|
@ -594,7 +680,6 @@ def send_tnc_state():
|
|||
"frequency": heard[6],
|
||||
}
|
||||
)
|
||||
|
||||
return json.dumps(output)
|
||||
|
||||
|
||||
|
@ -690,6 +775,16 @@ def process_daemon_commands(data):
|
|||
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]:
|
||||
log.debug(
|
||||
|
@ -725,6 +820,7 @@ def process_daemon_commands(data):
|
|||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
ssid_list,
|
||||
]
|
||||
)
|
||||
command_response("start_tnc", True)
|
||||
|
@ -820,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,7 @@ Not nice, suggestions are appreciated :-)
|
|||
import subprocess
|
||||
from enum import Enum
|
||||
|
||||
VERSION = "0.6.7-alpha.1"
|
||||
VERSION = "0.6.12-alpha.6"
|
||||
|
||||
ENABLE_EXPLORER = False
|
||||
|
||||
|
@ -25,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""
|
||||
|
@ -73,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
|
||||
|
@ -81,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_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 = 3
|
||||
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"
|
||||
|
|
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