diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9784910b..039946ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,7 +13,14 @@ updates: directory: "/gui" schedule: interval: "daily" - + + # Maintain dependencies for npm + - package-ecosystem: "npm" + directory: "/gui" + schedule: + interval: "daily" + target-branch: "ls-gui-single-pager" + # Maintain dependencies for pip - package-ecosystem: "pip" directory: "/" diff --git a/.github/workflows/build_multiplatform.yml b/.github/workflows/build_multiplatform.yml index 6128bca9..8d6533b0 100644 --- a/.github/workflows/build_multiplatform.yml +++ b/.github/workflows/build_multiplatform.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12] + os: [ubuntu-20.04, ubuntu-22.04, macos-latest, macos-12] platform: [{name: "native"}, {name: "Windows", file: "dll"}] architecture: [i686-w64-mingw32, x86_64-w64-mingw32] include: @@ -27,9 +27,9 @@ jobs: generator: Unix Makefiles shell: bash - - os: macos-11 + - os: macos-latest libcodec2_name: libcodec2.1.2.dylib - libcodec2_os_name: libcodec2_macos-11 + libcodec2_os_name: libcodec2_macos-latest libcodec2_filetype: dylib generator: Unix Makefiles shell: bash @@ -194,27 +194,27 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, macos-11, windows-latest] + os: [ubuntu-20.04, macos-latest, windows-latest] include: - os: ubuntu-20.04 - zip_name: ubuntu_tnc + zip_name: ubuntu_modem generator: Unix Makefiles daemon_binary_name: freedata-daemon - tnc_binary_name: freedata-tnc + modem_binary_name: freedata-modem electron_parameters: "-p always" - - os: macos-11 - zip_name: macos_tnc + - os: macos-latest + zip_name: macos_modem generator: Unix Makefiles daemon_binary_name: freedata-daemon - tnc_binary_name: freedata-tnc + modem_binary_name: freedata-modem electron_parameters: "-p always" - os: windows-latest - zip_name: windows_tnc + zip_name: windows_modem generator: Visual Studio 16 2019 daemon_binary_name: freedata-daemon.exe - tnc_binary_name: freedata-tnc.exe + modem_binary_name: freedata-modem.exe electron_parameters: "-p always --x64 --ia32" steps: - name: Checkout code for ${{ matrix.platform.name }} @@ -232,30 +232,30 @@ jobs: with: node-version: 18.17 - - name: Create tnc/dist - working-directory: tnc + - name: Create modem/dist + working-directory: modem run: | mkdir -p dist - - name: Create tnc/dist/tnc - working-directory: tnc + - name: Create modem/dist/modem + working-directory: modem run: | - mkdir -p dist/tnc + mkdir -p dist/modem - ##- name: Download libcodec2 artifact TNC DIST + ##- name: Download libcodec2 artifact Modem DIST ## uses: actions/download-artifact@v3 ## with: - ## path: tnc/dist/codec2 + ## path: modem/dist/codec2 - - name: create tnc/lib/codec2 - working-directory: tnc/lib/ + - name: create modem/lib/codec2 + working-directory: modem/lib/ run: | mkdir codec2 - - name: Download libcodec2 artifact TNC LIB + - name: Download libcodec2 artifact Modem LIB uses: actions/download-artifact@v3 with: - path: tnc/lib/codec2 + path: modem/lib/codec2 - name: Install Linux dependencies @@ -285,50 +285,50 @@ jobs: - name: Build binaries macOS if: ${{startsWith(matrix.os, 'macos')}} - working-directory: tnc + working-directory: modem run: | - # now build tnc binaries + # now build modem binaries pyinstaller -y freedata.spec # and to some final cleanup - # cp -r -f dist/tnc/* dist/ - # rm -r dist/tnc + # cp -r -f dist/modem/* dist/ + # rm -r dist/modem - name: Build binaries Linux and Windows if: ${{!startsWith(matrix.os, 'macos')}} - working-directory: tnc + working-directory: modem run: | # pyinstaller freedata.spec # python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile daemon.py -o ${{ matrix.daemon_binary_name }} - # python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.tnc_binary_name }} + # python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.modem_binary_name }} 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 if: ${{startsWith(matrix.os, 'ubuntu')}} - working-directory: tnc + working-directory: modem run: | - cp -r -f daemon.dist/* dist/tnc - cp -r -f main.dist/* dist/tnc + cp -r -f daemon.dist/* dist/modem + cp -r -f main.dist/* dist/modem - name: Copy binaries - Windows if: ${{startsWith(matrix.os, 'windows')}} - working-directory: tnc + working-directory: modem # These are powershell aliases, not UNIX commands. run: | - cp -r -Force daemon.dist/* dist/tnc - cp -r -Force main.dist/* dist/tnc + cp -r -Force daemon.dist/* dist/modem + cp -r -Force main.dist/* dist/modem - - name: Rename tnc binaries + - name: Rename modem binaries # we don't need renaming for pyinstaller builds as output name is defined if: ${{!startsWith(matrix.os, 'macos')}} - working-directory: tnc + working-directory: modem run: | - mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }} - mv dist/tnc/main* dist/tnc/${{ matrix.tnc_binary_name }} + mv dist/modem/daemon* dist/modem/${{ matrix.daemon_binary_name }} + mv dist/modem/main* dist/modem/${{ matrix.modem_binary_name }} - uses: actions/download-artifact@v3 with: - path: tnc/dist/tnc + path: modem/dist/modem - name: LIST ALL FILES @@ -336,20 +336,20 @@ jobs: - name: Download Portaudio binaries Linux macOS if: ${{!startsWith(matrix.os, 'windows')}} - working-directory: tnc + working-directory: modem run: | - if ! test -d "dist/tnc/_sounddevice_data"; then - git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries + if ! test -d "dist/modem/_sounddevice_data"; then + git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries fi - name: Download Portaudio binaries Windows if: ${{startsWith(matrix.os, 'windows')}} - working-directory: tnc + working-directory: modem run: | - if(Test-Path -Path "dist/tnc/_sounddevice_data"){ + if(Test-Path -Path "dist/modem/_sounddevice_data"){ echo "sounddevice folder already exists" } else { - git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries + git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries } - name: LIST ALL FILES @@ -362,53 +362,50 @@ jobs: # find . -type d -name .git -exec rm -r {} \; find . -type d -o -name ".git" -delete - - name: Build/release Electron app - uses: coparse-inc/action-electron-builder@v1.0.0 - env: + - name: Electron Builder + env: # Setting environment variables for the entire job + GH_TOKEN: ${{ secrets.github_token }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - with: - package_root: "./gui/" - github_token: ${{ secrets.github_token }} - # If the commit is tagged with a version (e.g. "v1.0.0"), - # release the app after building - release: ${{ startsWith(github.ref, 'refs/tags/v') }} - args: ${{ matrix.electron_parameters }} - max_attempts: 3 + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + working-directory: gui + run: | + npm i + npm run build - - name: Compress TNC + - name: Compress Modem uses: thedoctor0/zip-release@master with: type: 'zip' filename: '${{ matrix.zip_name }}.zip' - # directory: ./tnc/dist/tnc - directory: ./tnc/dist/tnc + # directory: ./modem/dist/modem + directory: ./modem/dist/modem path: . # exclusions: '*.git* /*node_modules/* .editorconfig' - - name: Release TNC + - name: Release Modem 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 + files: ./modem/dist/modem/${{ matrix.zip_name }}.zip + #files: ./modem/dist/${{ matrix.zip_name }}.zip - name: LIST ALL FILES run: ls -R - #- name: Upload TNC artifacts + #- name: Upload Modem artifacts # uses: actions/upload-artifact@v3 # if: ${{!startsWith(github.ref, 'refs/tags/v')}} # with: # name: ${{ matrix.zip_name }}.zip - # # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip - # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip# + # # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip + # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip# #- name: Upload App bundle artifacts # uses: actions/upload-artifact@v3 # if: ${{!startsWith(github.ref, 'refs/tags/v')}} # with: # name: app_bundle_${{ matrix.os }}.zip - # # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip + # # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip # path: ./gui/dist/* diff --git a/.gitignore b/.gitignore index 350c65f8..ce0407d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# possible installation of codec2 within tnc -tnc/codec2 +# possible installation of codec2 within modem +modem/codec2 # temporary test artifacts **/build @@ -22,3 +22,11 @@ package-lock.json *.raw coverage.sh coverage.xml + +#ignore node_modules +/gui/node_modules/ + +#Ignore gui build items +/gui/dist +/gui/release +/gui/dist-electron \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e41c8610..e6d53db2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,43 +24,43 @@ set(TESTFRAMES 3) add_test(NAME audio_buffer COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; python3 test_audiobuffer.py") set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") add_test(NAME resampler COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; python3 test_resample_48_8.py") set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS") -add_test(NAME tnc_state_machine +add_test(NAME modem_state_machine COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_tnc_states.py") - set_tests_properties(tnc_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") + python3 test_modem_states.py") + set_tests_properties(modem_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") -add_test(NAME tnc_irs_iss +add_test(NAME modem_irs_iss COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_tnc.py") - set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") + python3 test_modem.py") + set_tests_properties(modem_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") # disabled this test as its actually broken since we introduced session IDs #add_test(NAME chat_text # COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; -# export PYTHONPATH=../tnc; +# export PYTHONPATH=../modem; # cd ${CMAKE_CURRENT_SOURCE_DIR}/test; # python3 test_chat_text.py") # set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") add_test(NAME datac13_frames COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; python3 test_datac13.py") set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") @@ -68,21 +68,21 @@ add_test(NAME datac13_frames # disabled this test as its actually broken since we introduced dataclasses #add_test(NAME datac13_frames_negative # COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; -# export PYTHONPATH=../tnc; +# export PYTHONPATH=../modem; # cd ${CMAKE_CURRENT_SOURCE_DIR}/test; # python3 test_datac13_negative.py") # set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") add_test(NAME helper_routines COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; python3 test_helpers.py") set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") add_test(NAME py_highsnr_stdio_P_P_multi COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; export BURSTS=${BURSTS}; export FRAMESPERBURST=${FRAMESPERBURST}; PATH=$PATH:${CODEC2_BUILD_DIR}/src; @@ -92,7 +92,7 @@ add_test(NAME py_highsnr_stdio_P_P_multi add_test(NAME py_highsnr_stdio_P_P_datacx COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; export BURSTS=${BURSTS}; export FRAMESPERBURST=${FRAMESPERBURST}; PATH=$PATH:${CODEC2_BUILD_DIR}/src; @@ -102,7 +102,7 @@ add_test(NAME py_highsnr_stdio_P_P_datacx add_test(NAME py_highsnr_stdio_P_C_datacx COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; export BURSTS=${BURSTS}; export FRAMESPERBURST=${FRAMESPERBURST}; PATH=$PATH:${CODEC2_BUILD_DIR}/src; @@ -112,7 +112,7 @@ add_test(NAME py_highsnr_stdio_P_C_datacx add_test(NAME py_highsnr_stdio_C_P_datacx COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; + export PYTHONPATH=../modem; export BURSTS=${BURSTS}; export FRAMESPERBURST=${FRAMESPERBURST}; export TESTFRAMES=${TESTFRAMES}; diff --git a/gui/.eslintrc.json b/gui/.eslintrc.json new file mode 100644 index 00000000..61154ac0 --- /dev/null +++ b/gui/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": [ + "plugin:vue/vue3-essential", + "plugin:prettier/recommended", + "eslint:recommended" + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": [ + "vue" + ], + "ignorePatterns": ["**/src/assets/*", "**/src/js/deprecated*", "**/node_modules"], + "rules": { + } +} diff --git a/gui/.gitignore b/gui/.gitignore deleted file mode 100644 index 67045665..00000000 --- a/gui/.gitignore +++ /dev/null @@ -1,104 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port diff --git a/gui/README.md b/gui/README.md new file mode 100644 index 00000000..ef72fd52 --- /dev/null +++ b/gui/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + + + + + \ No newline at end of file diff --git a/gui/main.js b/gui/main.js deleted file mode 100644 index 437405a4..00000000 --- a/gui/main.js +++ /dev/null @@ -1,1083 +0,0 @@ -const { app, BrowserWindow, ipcMain, dialog, shell } = require("electron"); -const https = require("https"); -const { autoUpdater } = require("electron-updater"); -const path = require("path"); -const fs = require("fs"); -const os = require("os"); -const spawn = require("child_process").spawn; - -const log = require("electron-log"); -const mainLog = log.scope("main"); -const daemonProcessLog = log.scope("freedata-daemon"); -const mime = require("mime"); -const net = require("net"); -const FD = require("./freedata"); - -//Useful for debugging event emitter memory leaks -//require('events').EventEmitter.defaultMaxListeners = 10; -//process.traceProcessWarnings=true; - -const sysInfo = log.scope("system information"); -sysInfo.info("SYSTEM INFORMATION ----------------------------- "); -sysInfo.info("APP VERSION : " + app.getVersion()); -sysInfo.info("PLATFORM : " + os.platform()); -sysInfo.info("ARCHITECTURE: " + os.arch()); -sysInfo.info("FREE MEMORY: " + os.freemem()); -sysInfo.info("TOTAL MEMORY: " + os.totalmem()); -sysInfo.info("LOAD AVG : " + os.loadavg()); -sysInfo.info("RELEASE : " + os.release()); -sysInfo.info("TYPE : " + os.type()); -sysInfo.info("VERSION : " + os.version()); -sysInfo.info("UPTIME : " + os.uptime()); - -app.setName("FreeDATA"); - -var appDataFolder = - process.env.APPDATA || - (process.platform == "darwin" - ? process.env.HOME + "/Library/Application Support" - : process.env.HOME + "/.config"); -var configFolder = path.join(appDataFolder, "FreeDATA"); -var configPath = path.join(configFolder, "config.json"); - -// create config folder if not exists -if (!fs.existsSync(configFolder)) { - fs.mkdirSync(configFolder); -} - -// create config file if not exists with defaults -const configDefaultSettings = - '{\ - "tnc_host": "127.0.0.1",\ - "tnc_port": "3000",\ - "daemon_host": "127.0.0.1",\ - "daemon_port": "3001",\ - "mycall": "AA0AA-0",\ - "mygrid": "JN40aa",\ - "radiocontrol" : "disabled",\ - "hamlib_deviceid": "RIG_MODEL_DUMMY_NOVFO",\ - "hamlib_deviceport": "ignore",\ - "hamlib_stop_bits": "ignore",\ - "hamlib_data_bits": "ignore",\ - "hamlib_handshake": "ignore",\ - "hamlib_serialspeed": "ignore",\ - "hamlib_dtrstate": "ignore",\ - "hamlib_pttprotocol": "ignore",\ - "hamlib_ptt_port": "ignore",\ - "hamlib_dcd": "ignore",\ - "hamlbib_serialspeed_ptt": "9600",\ - "hamlib_rigctld_port" : "4532",\ - "hamlib_rigctld_ip" : "127.0.0.1",\ - "hamlib_rigctld_path" : "",\ - "hamlib_rigctld_server_port" : "4532",\ - "hamlib_rigctld_custom_args": "",\ - "tci_port" : "50001",\ - "tci_ip" : "127.0.0.1",\ - "spectrum": "waterfall",\ - "tnclocation": "localhost",\ - "enable_scatter" : "False",\ - "enable_fft" : "False",\ - "enable_fsk" : "False",\ - "low_bandwidth_mode" : "False",\ - "theme" : "default",\ - "screen_height" : 430,\ - "screen_width" : 1050,\ - "update_channel" : "latest",\ - "beacon_interval" : 300,\ - "received_files_folder" : "None",\ - "tuning_range_fmin" : "-50.0",\ - "tuning_range_fmax" : "50.0",\ - "respond_to_cq" : "True",\ - "rx_buffer_size" : "16", \ - "enable_explorer" : "False", \ - "wftheme": 2, \ - "high_graphics" : "True",\ - "explorer_stats" : "False", \ - "auto_tune" : "False", \ - "enable_is_writing" : "True", \ - "shared_folder_path" : ".", \ - "enable_request_profile" : "True", \ - "enable_request_shared_folder" : "False", \ - "max_retry_attempts" : 5, \ - "enable_auto_retry" : "False", \ - "tx_delay" : 0, \ - "auto_start": 0, \ - "enable_sys_notification": 1, \ - "enable_mesh_features": "False" \ - }'; - -if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, configDefaultSettings); -} - -// load settings -var config = require(configPath); - -//config validation -// check running config against default config. -// if parameter not exists, add it to running config to prevent errors -sysInfo.info("CONFIG VALIDATION ----------------------------- "); - -var parsedConfig = JSON.parse(configDefaultSettings); -for (key in parsedConfig) { - if (config.hasOwnProperty(key)) { - sysInfo.info("FOUND SETTTING [" + key + "]: " + config[key]); - } else { - sysInfo.error("MISSING SETTTING [" + key + "] : " + parsedConfig[key]); - config[key] = parsedConfig[key]; - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - } -} -sysInfo.info("------------------------------------------ "); -/* -var chatDB = path.join(configFolder, 'chatDB.json') -// create chat database file if not exists -const configContentChatDB = ` -{ "chatDB" : [{ - "id" : "00000000", - "timestamp" : 1234566, - "mycall" : "AA0AA", - "dxcall" : "AB0AB", - "dxgrid" : "JN1200", - "message" : "hallowelt" -}] -} -`; -if (!fs.existsSync(chatDB)) { - fs.writeFileSync(chatDB, configContentChatDB); -} -*/ - -/* -// Creates receivedFiles folder if not exists -// https://stackoverflow.com/a/26227660 -var appDataFolder = process.env.HOME -var applicationFolder = path.join(appDataFolder, "FreeDATA"); -var receivedFilesFolder = path.join(applicationFolder, "receivedFiles"); - -// https://stackoverflow.com/a/13544465 -fs.mkdir(receivedFilesFolder, { - recursive: true -}, function(err) { - console.log(err); -}); - -*/ - -let win = null; -let data = null; -let logViewer = null; -let meshViewer = null; -var daemonProcess = null; - -// create a splash screen -function createSplashScreen() { - splashScreen = new BrowserWindow({ - height: 250, - width: 250, - transparent: true, - frame: false, - alwaysOnTop: true, - }); - splashScreen.loadFile("src/splash.html"); - splashScreen.center(); -} - -function createWindow() { - win = new BrowserWindow({ - width: config.screen_width, - height: config.screen_height, - show: false, - autoHideMenuBar: true, - icon: "src/img/icon.png", - webPreferences: { - //preload: path.join(__dirname, 'preload-main.js'), - backgroundThrottle: false, - preload: require.resolve("./preload-main.js"), - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: false, - sandbox: false, - //https://stackoverflow.com/questions/53390798/opening-new-window-electron/53393655 - //https://github.com/electron/remote - }, - }); - // hide menu bar - win.setMenuBarVisibility(false); - - //open dev tools - /*win.webContents.openDevTools({ - mode: 'undocked', - activate: true, - }) - */ - win.loadFile("src/index.html"); - - chat = new BrowserWindow({ - height: 600, - width: 1000, - show: false, - //parent: win, - webPreferences: { - preload: require.resolve("./preload-chat.js"), - nodeIntegration: true, - }, - }); - - chat.loadFile("src/chat-module.html"); - chat.setMenuBarVisibility(false); - - logViewer = new BrowserWindow({ - height: 900, - width: 600, - show: false, - //parent: win, - webPreferences: { - preload: require.resolve("./preload-log.js"), - nodeIntegration: true, - }, - }); - - logViewer.loadFile("src/log-module.html"); - logViewer.setMenuBarVisibility(false); - - // Emitted when the window is closed. - logViewer.on("close", function (evt) { - if (logViewer !== null) { - evt.preventDefault(); - logViewer.hide(); - } else { - this.close(); - } - }); - - meshViewer = new BrowserWindow({ - height: 900, - width: 600, - show: false, - //parent: win, - webPreferences: { - preload: require.resolve("./preload-mesh.js"), - nodeIntegration: true, - }, - }); - - meshViewer.loadFile("src/mesh-module.html"); - meshViewer.setMenuBarVisibility(false); - - // Emitted when the window is closed. - meshViewer.on("close", function (evt) { - if (meshViewer !== null) { - evt.preventDefault(); - meshViewer.hide(); - } else { - this.close(); - } - }); - // Emitted when the window is closed. - win.on("closed", function () { - console.log("closing all windows....."); - /* - win = null; - chat = null; - logViewer = null; - */ - close_all(); - }); - - win.once("ready-to-show", () => { - log.transports.file.level = "debug"; - autoUpdater.logger = log.scope("updater"); - - autoUpdater.channel = config.update_channel; - - autoUpdater.autoInstallOnAppQuit = false; - autoUpdater.autoDownload = true; - autoUpdater.checkForUpdatesAndNotify(); - //autoUpdater.quitAndInstall(); - }); - - chat.on("closed", function () {}); - - // https://stackoverflow.com/questions/44258831/only-hide-the-window-when-closing-it-electron - chat.on("close", function (evt) { - evt.preventDefault(); - chat.hide(); - }); -} - -app.whenReady().then(() => { - // show splash screen - createSplashScreen(); - - // create main window - createWindow(); - - // wait some time, then close splash screen and show main windows - setTimeout(function () { - splashScreen.close(); - win.show(); - }, 3000); - - //Generate daemon binary path - var daemonPath = ""; - switch (os.platform().toLowerCase()) { - case "darwin": - case "linux": - daemonPath = path.join(process.resourcesPath, "tnc", "freedata-daemon"); - - break; - case "win32": - case "win64": - daemonPath = path.join( - process.resourcesPath, - "tnc", - "freedata-daemon.exe", - ); - break; - default: - console.log("Unhandled OS Platform: ", os.platform()); - break; - } - - //Start daemon binary if it exists - if (fs.existsSync(daemonPath)) { - mainLog.info("Starting freedata-daemon binary"); - daemonProcess = spawn(daemonPath, [], { - cwd: path.join(daemonPath, ".."), - }); - // return process messages - daemonProcess.on("error", (err) => { - daemonProcessLog.error(`error when starting daemon: ${err}`); - }); - daemonProcess.on("message", (data) => { - daemonProcessLog.info(`${data}`); - }); - daemonProcess.stdout.on("data", (data) => { - daemonProcessLog.info(`${data}`); - }); - daemonProcess.stderr.on("data", (data) => { - daemonProcessLog.info(`${data}`); - let arg = { - entry: `${data}`, - }; - // send info to log only if log screen available - // it seems an error occurs when updating - if (logViewer !== null && logViewer !== "") { - try { - logViewer.webContents.send("action-update-log", arg); - } catch (e) { - // empty for keeping error stuff silent - // this is important to avoid error messages if we are going to close the app while - // an logging information will be pushed to the logger - } - } - }); - daemonProcess.on("close", (code) => { - daemonProcessLog.warn(`daemonProcess exited with code ${code}`); - }); - } else { - daemonProcess = null; - daemonPath = null; - mainLog.info("Daemon binary doesn't exist--normal for dev environments."); - } - win.send("action-set-app-version", app.getVersion()); -}); - -app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } -}); - -app.on("window-all-closed", () => { - close_all(); -}); - -// IPC HANDLER -//Update configuration globally -ipcMain.on("set-config-global", (event, data) => { - config = data; - win.webContents.send("update-config", config); - chat.webContents.send("update-config", config); - //console.log("set-config-global called"); -}); - -//Show/update task bar/button progressbar -ipcMain.on("request-show-electron-progressbar", (event, data) => { - win.setProgressBar(data / 100); -}); - -ipcMain.on("request-show-chat-window", () => { - chat.show(); -}); - -ipcMain.on("request-clear-chat-connected", () => { - //Clear chat window's connected with text - chat.webContents.send("action-clear-reception-status"); -}); - -ipcMain.on("request-update-dbclean-spinner", () => { - //Turn off dbclean spinner - win.webContents.send("action-update-dbclean-spinner"); -}); - -// UPDATE TNC CONNECTION -ipcMain.on("request-update-tnc-ip", (event, data) => { - win.webContents.send("action-update-tnc-ip", data); -}); - -// UPDATE DAEMON CONNECTION -ipcMain.on("request-update-daemon-ip", (event, data) => { - win.webContents.send("action-update-daemon-ip", data); -}); - -ipcMain.on("request-update-tnc-state", (event, arg) => { - win.webContents.send("action-update-tnc-state", arg); - meshViewer.send("action-update-mesh-table", arg); - //data.webContents.send('action-update-tnc-state', arg); -}); - -/* -ipcMain.on('request-update-data-state', (event, arg) => { - //win.webContents.send('action-update-data-state', arg); - //data.webContents.send('action-update-data-state', arg); -}); - -ipcMain.on('request-update-heard-stations', (event, arg) => { - win.webContents.send('action-update-heard-stations', arg); -}); -*/ -ipcMain.on("request-update-daemon-state", (event, arg) => { - win.webContents.send("action-update-daemon-state", arg); -}); - -ipcMain.on("request-update-hamlib-test", (event, arg) => { - win.webContents.send("action-update-hamlib-test", arg); -}); - -ipcMain.on("request-update-tnc-connection", (event, arg) => { - win.webContents.send("action-update-tnc-connection", arg); -}); - -ipcMain.on("request-update-daemon-connection", (event, arg) => { - win.webContents.send("action-update-daemon-connection", arg); -}); - -ipcMain.on("run-tnc-command", (event, arg) => { - win.webContents.send("run-tnc-command", arg); -}); - -ipcMain.on("tnc-fec-iswriting", (event, arg) => { - win.webContents.send("run-tnc-command-fec-iswriting"); -}); - -ipcMain.on("request-update-rx-buffer", (event, arg) => { - win.webContents.send("action-update-rx-buffer", arg); -}); - -/* -ipcMain.on('request-update-rx-msg-buffer', (event, arg) => { - chat.webContents.send('action-update-rx-msg-buffer', arg); -}); -*/ -ipcMain.on("request-new-msg-received", (event, arg) => { - chat.webContents.send("action-new-msg-received", arg); -}); -ipcMain.on("request-update-transmission-status", (event, arg) => { - chat.webContents.send("action-update-transmission-status", arg); - win.webContents.send("action-update-transmission-status", arg); -}); - -ipcMain.on("request-update-reception-status", (event, arg) => { - win.webContents.send("action-update-reception-status", arg); - chat.webContents.send("action-update-reception-status", arg); -}); - -//Called by main to query chat if there are new messages -ipcMain.on("request-update-unread-messages", () => { - //mainLog.info("Got request to check if chat has new messages") - chat.webContents.send("action-update-unread-messages"); -}); -//Called by chat to notify main if there are new messages -ipcMain.on("request-update-unread-messages-main", (event, arg) => { - win.webContents.send("action-update-unread-messages-main", arg); - //mainLog.info("Received reply from chat and ?new messages = " +arg); -}); - -//Called by main to notify chat we should clean the DB -ipcMain.on("request-clean-db", () => { - chat.webContents.send("action-clean-db"); -}); - -ipcMain.on("request-open-tnc-log", () => { - logViewer.show(); -}); - -ipcMain.on("request-open-mesh-module", () => { - meshViewer.show(); -}); - -//file selector -ipcMain.on("get-file-path", (event, data) => { - dialog - .showOpenDialog({ - defaultPath: path.join(__dirname, "../"), - buttonLabel: "Select File", - properties: ["openFile"], - }) - .then((filePaths) => { - if (filePaths.canceled == false) { - win.webContents.send(data.action, { path: filePaths }); - } - }); -}); - -//folder selector -ipcMain.on("get-folder-path", (event, data) => { - dialog - .showOpenDialog({ - defaultPath: path.join(__dirname, "../"), - buttonLabel: "Select folder", - properties: ["openDirectory"], - }) - .then((folderPaths) => { - if (folderPaths.canceled == false) { - win.webContents.send(data.action, { path: folderPaths }); - //win.webContents.send(data.action, { path: filePaths }); - } - }); -}); - -//open folder -ipcMain.on("open-folder", (event, data) => { - shell.showItemInFolder(data.path); -}); - -//select file -ipcMain.on("select-file", (event, data) => { - dialog - .showOpenDialog({ - defaultPath: path.join(__dirname, "../"), - buttonLabel: "Select file", - properties: ["openFile"], - }) - .then((filepath) => { - console.log(filepath.filePaths[0]); - - try { - //fs.readFile(filepath.filePaths[0], 'utf8', function (err, data) { - //Has to be binary - fs.readFile(filepath.filePaths[0], "binary", function (err, data) { - console.log(data.length); - - console.log(data); - - var filename = path.basename(filepath.filePaths[0]); - var mimeType = mime.getType(filename); - console.log(mimeType); - if (mimeType == "" || mimeType == null) { - mimeType = "plain/text"; - } - chat.webContents.send("return-selected-files", { - data: data, - mime: mimeType, - filename: filename, - }); - }); - } catch (err) { - console.log(err); - } - }); -}); - -//select image file -ipcMain.on("select-user-image", (event, data) => { - dialog - .showOpenDialog({ - defaultPath: path.join(__dirname, "../"), - buttonLabel: "Select file", - properties: ["openFile"], - }) - .then((filepath) => { - console.log(filepath.filePaths[0]); - - try { - // read data as base64 which makes conversion to blob easier - fs.readFile(filepath.filePaths[0], "base64", function (err, data) { - var filename = path.basename(filepath.filePaths[0]); - var mimeType = mime.getType(filename); - - if (mimeType == "" || mimeType == null) { - mimeType = "plain/text"; - } - - chat.webContents.send("return-select-user-image", { - data: data, - mime: mimeType, - filename: filename, - }); - }); - } catch (err) { - console.log(err); - } - }); -}); - -// read files in folder - use case "shared folder" -ipcMain.on("read-files-in-folder", (event, data) => { - let fileList = []; - if (config["enable_request_shared_folder"].toLowerCase() == "false") { - //mainLog.info("Shared file folder is disable, not populating fileList"); - chat.webContents.send("return-shared-folder-files", { - files: fileList, - }); - return; - } - let folder = data.folder; - let files = fs.readdirSync(folder); - console.log(folder); - console.log(files); - files.forEach((file) => { - try { - let filePath = folder + "/" + file; - if (fs.lstatSync(filePath).isFile()) { - let fileSizeInBytes = fs.statSync(filePath).size; - let extension = path.extname(filePath); - fileList.push({ - name: file, - extension: extension.substring(1), - size: fileSizeInBytes, - }); - } - } catch (err) { - console.log(err); - } - }); - - chat.webContents.send("return-shared-folder-files", { - files: fileList, - }); -}); - -//save file to folder -ipcMain.on("save-file-to-folder", (event, data) => { - console.log(data.file); - - dialog.showSaveDialog({ defaultPath: data.filename }).then((filepath) => { - console.log(filepath.filePath); - console.log(data.file); - - try { - let arraybuffer = Buffer.from(data.file, "base64").toString("utf-8"); - console.log(arraybuffer); - //Has to be binary - fs.writeFile( - filepath.filePath, - arraybuffer, - "binary", - function (err, data) {}, - ); - } catch (err) { - console.log(err); - } - }); -}); - -//tnc messages START -------------------------------------- - -// FEC iswriting received -ipcMain.on("request-show-fec-toast-iswriting", (event, data) => { - win.webContents.send("action-show-fec-toast-iswriting", data); - chat.webContents.send("action-show-feciswriting", data); -}); - -// CQ TRANSMITTING -ipcMain.on("request-show-cq-toast-transmitting", (event, data) => { - win.webContents.send("action-show-cq-toast-transmitting", data); -}); - -// CQ RECEIVED -ipcMain.on("request-show-cq-toast-received", (event, data) => { - win.webContents.send("action-show-cq-toast-received", data); -}); - -// QRV TRANSMITTING -ipcMain.on("request-show-qrv-toast-transmitting", (event, data) => { - win.webContents.send("action-show-qrv-toast-transmitting", data); -}); - -// QRV RECEIVED -ipcMain.on("request-show-qrv-toast-received", (event, data) => { - win.webContents.send("action-show-qrv-toast-received", data); -}); - -// BEACON TRANSMITTING -ipcMain.on("request-show-beacon-toast-transmitting", (event, data) => { - win.webContents.send("action-show-beacon-toast-transmitting", data); -}); - -// BEACON RECEIVED -ipcMain.on("request-show-beacon-toast-received", (event, data) => { - win.webContents.send("action-show-beacon-toast-received", data); -}); - -// PING TRANSMITTING -ipcMain.on("request-show-ping-toast-transmitting", (event, data) => { - win.webContents.send("action-show-ping-toast-transmitting", data); -}); - -// PING RECEIVED -ipcMain.on("request-show-ping-toast-received", (event, data) => { - win.webContents.send("action-show-ping-toast-received", data); -}); - -// PING RECEIVED ACK -ipcMain.on("request-show-ping-toast-received-ack", (event, data) => { - win.webContents.send("action-show-ping-toast-received-ack", data); -}); - -// ARQ DATA CHANNEL OPENING -ipcMain.on("request-show-arq-toast-datachannel-opening", (event, data) => { - win.webContents.send("action-show-arq-toast-datachannel-opening", data); -}); - -// ARQ DATA CHANNEL WAITING -ipcMain.on("request-show-arq-toast-datachannel-waiting", (event, data) => { - win.webContents.send("action-show-arq-toast-datachannel-waiting", data); -}); - -// ARQ DATA CHANNEL OPEN -ipcMain.on("request-show-arq-toast-datachannel-opened", (event, data) => { - win.webContents.send("action-show-arq-toast-datachannel-opened", data); -}); - -// ARQ DATA RECEIVED OPENER -ipcMain.on( - "request-show-arq-toast-datachannel-received-opener", - (event, data) => { - win.webContents.send( - "action-show-arq-toast-datachannel-received-opener", - data, - ); - }, -); - -// ARQ TRANSMISSION FAILED -ipcMain.on("request-show-arq-toast-transmission-failed", (event, data) => { - win.webContents.send("action-show-arq-toast-transmission-failed", data); -}); - -// ARQ TRANSMISSION FAILED -ipcMain.on("request-show-arq-toast-transmission-failed-ver", (event, data) => { - win.webContents.send("action-show-arq-toast-transmission-failed-ver", data); -}); - -// ARQ TRANSMISSION RECEIVING -ipcMain.on("request-show-arq-toast-transmission-receiving", (event, data) => { - win.webContents.send("action-show-arq-toast-transmission-receiving", data); -}); - -// ARQ TRANSMISSION RECEIVED -ipcMain.on("request-show-arq-toast-transmission-received", (event, data) => { - win.webContents.send("action-show-arq-toast-transmission-received", data); -}); - -// ARQ TRANSMISSION TRANSMITTING -ipcMain.on( - "request-show-arq-toast-transmission-transmitting", - (event, data) => { - win.webContents.send( - "action-show-arq-toast-transmission-transmitting", - data, - ); - }, -); - -// ARQ TRANSMISSION TRANSMITTED -ipcMain.on("request-show-arq-toast-transmission-transmitted", (event, data) => { - win.webContents.send("action-show-arq-toast-transmission-transmitted", data); -}); - -// ARQ SESSION CONNECTING -ipcMain.on("request-show-arq-toast-session-connecting", (event, data) => { - win.webContents.send("action-show-arq-toast-session-connecting", data); -}); - -// ARQ SESSION WAITING -ipcMain.on("request-show-arq-toast-session-waiting", (event, data) => { - win.webContents.send("action-show-arq-toast-session-waiting", data); -}); - -// ARQ SESSION CONNECTED -ipcMain.on("request-show-arq-toast-session-connected", (event, data) => { - win.webContents.send("action-show-arq-toast-session-connected", data); -}); - -// ARQ SESSION CLOSE -ipcMain.on("request-show-arq-toast-session-close", (event, data) => { - win.webContents.send("action-show-arq-toast-session-close", data); -}); - -// ARQ SESSION FAILED -ipcMain.on("request-show-arq-toast-session-failed", (event, data) => { - win.webContents.send("action-show-arq-toast-session-failed", data); -}); - -//tnc messages END -------------------------------------- - -//restart and install udpate -ipcMain.on("request-restart-and-install", (event, data) => { - close_sub_processes(); - autoUpdater.quitAndInstall(); -}); - -// LISTENER FOR UPDATER EVENTS -autoUpdater.on("update-available", (info) => { - mainLog.info("update available"); - - let arg = { - status: "update-available", - info: info, - }; - win.webContents.send("action-updater", arg); -}); - -autoUpdater.on("update-not-available", (info) => { - mainLog.info("update not available"); - let arg = { - status: "update-not-available", - info: info, - }; - win.webContents.send("action-updater", arg); -}); - -autoUpdater.on("update-downloaded", (info) => { - mainLog.info("update downloaded"); - let arg = { - status: "update-downloaded", - info: info, - }; - win.webContents.send("action-updater", arg); - // we need to call this at this point. - // if an update is available and we are force closing the app - // the entire screen crashes... - //mainLog.info('quit application and install update'); - //autoUpdater.quitAndInstall(); -}); - -autoUpdater.on("checking-for-update", () => { - mainLog.info("checking for update"); - let arg = { - status: "checking-for-update", - version: app.getVersion(), - }; - win.webContents.send("action-updater", arg); -}); - -autoUpdater.on("download-progress", (progress) => { - let arg = { - status: "download-progress", - progress: progress, - }; - win.webContents.send("action-updater", arg); -}); - -autoUpdater.on("error", (error) => { - mainLog.info("update error"); - let arg = { - status: "error", - progress: error, - }; - win.webContents.send("action-updater", arg); - mainLog.error("AUTO UPDATER : " + error); -}); - -function close_sub_processes() { - mainLog.warn("closing sub processes"); - - // closing the tnc binary if not closed when closing application and also our daemon which has been started by the gui - try { - if (daemonProcess != null) { - daemonProcess.kill(); - } - } catch (e) { - mainLog.error(e); - } - - mainLog.warn("closing tnc and daemon"); - try { - if (os.platform() == "win32" || os.platform() == "win64") { - spawn("Taskkill", ["/IM", "freedata-tnc.exe", "/F"]); - spawn("Taskkill", ["/IM", "freedata-daemon.exe", "/F"]); - } - - if (os.platform() == "linux") { - spawn("pkill", ["-9", "freedata-tnc"]); - spawn("pkill", ["-9", "freedata-daemon"]); - } - - if (os.platform() == "darwin") { - spawn("pkill", ["-9", "freedata-tnc"]); - spawn("pkill", ["-9", "freedata-daemon"]); - } - } catch (e) { - mainLog.error(e); - } -} - -function close_all() { - // function for closing the application with closing all used processes - - close_sub_processes(); - - mainLog.warn("quitting app"); - - win.destroy(); - chat.destroy(); - logViewer.destroy(); - meshViewer.destroy(); - - app.quit(); -} - -// RUN RIGCTLD -ipcMain.on("request-start-rigctld", (event, data) => { - try { - let rigctld_proc = spawn(data.path, data.parameters, { - windowsVerbatimArguments: true, - }); - - rigctld_proc.on("exit", function (code) { - console.log("rigctld process exited with code " + code); - - // if rigctld crashes, error code is -2 - // then we are going to restart rigctld - // this "fixes" a problem with latest rigctld on raspberry pi - //if (code == -2){ - // setTimeout(ipcRenderer.send('request-start-rigctld', data), 500); - //} - //let rigctld_proc = spawn(data.path, data.parameters); - }); - } catch (e) { - console.log(e); - } - - /* - const rigctld = exec(data.path, data.parameters); - rigctld.stdout.on("data", data => { - console.log(`stdout: ${data}`); - }); - */ -}); - -// STOP RIGCTLD -ipcMain.on("request-stop-rigctld", (event, data) => { - mainLog.warn("closing rigctld"); - try { - if (os.platform() == "win32" || os.platform() == "win64") { - spawn("Taskkill", ["/IM", "rigctld.exe", "/F"]); - } - - if (os.platform() == "linux") { - spawn("pkill", ["-9", "rigctld"]); - } - - if (os.platform() == "darwin") { - spawn("pkill", ["-9", "rigctld"]); - } - } catch (e) { - mainLog.error(e); - } -}); - -// CHECK RIGCTLD CONNECTION -// create new socket so we are not reopening every time a new one -var rigctld_connection = new net.Socket(); -var rigctld_connection_state = false; -var rigctld_events_wired = false; - -ipcMain.on("request-check-rigctld", (event, data) => { - try { - let Data = { - state: "unknown", - }; - - if (!rigctld_connection_state) { - rigctld_connection = new net.Socket(); - rigctld_events_wired = false; - rigctld_connection.connect(data.port, data.ip); - } - - // Check if we have created a new socket object and attach listeners if not already created - if (typeof rigctld_connection != "undefined" && !rigctld_events_wired) { - rigctld_connection.on("connect", function () { - rigctld_events_wired = true; - mainLog.info("Starting rigctld event listeners"); - rigctld_connection_state = true; - Data["state"] = "Connected"; - Data["active"] = true; - if (win !== null && win !== "" && typeof win != "undefined") { - // try catch for being sure we have a clean app close - try { - win.webContents.send("action-check-rigctld", Data); - } catch (e) { - console.log(e); - } - } - }); - - rigctld_connection.on("error", function () { - rigctld_connection_state = false; - Data["state"] = "Not Connected"; - Data["active"] = false; - if (win !== null && win !== "" && typeof win != "undefined") { - // try catch for being sure we have a clean app close - try { - win.webContents.send("action-check-rigctld", Data); - } catch (e) { - console.log(e); - } - } - }); - - rigctld_connection.on("end", function () { - rigctld_connection_state = false; - }); - } - } catch (e) { - console.log(e); - } -}); - -function downloadJsonUrlToFile(url, callsignPath) { - // https://nodejs.org/api/https.html#httpsgetoptions-callback - - https - .get(url, (res) => { - //console.log("statusCode:", res.statusCode); - //console.log("headers:", res.headers); - - res.on("data", (d) => { - //console.log(d); - let json = JSON.parse(d); - fs.writeFileSync(callsignPath, JSON.stringify(json, null, 2)); - sysInfo.info( - "Download " + url + " return statuscode: " + res.statusCode, - ); - }); - }) - .on("error", (e) => { - console.error(e); - }); -} -function downloadCallsignReverseLookupData() { - sysInfo.info("Downloading callsigns.json"); - var callsignPath = path.join(configFolder, "callsigns.json"); - downloadJsonUrlToFile( - "https://api.freedata.app/callsign_lookup.php", - callsignPath, - ); -} - -downloadCallsignReverseLookupData(); diff --git a/gui/package.json b/gui/package.json index 9e34a348..ea00c061 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,36 +1,38 @@ { "name": "FreeDATA", - "version": "0.10.2-alpha.1", "description": "FreeDATA ", - "main": "main.js", + "private": true, + "version": "0.11.0-alpha.1", + "main": "dist-electron/main/index.js", "scripts": { - "start": "electron .", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "engines": { - "node": ">=18.17.0", - "npm": ">=9.0.0" + "start": "git pull && npm i && vite", + "dev": "vite", + "check": "vue-tsc --noEmit", + "build": "vue-tsc --noEmit && vite build && electron-builder", + "preview": "vite preview", + "lint": "eslint --ext .js,.vue src", + "lint-fix": "eslint --ext .js,.vue --fix src" }, "repository": { "type": "git", "url": "https://github.com/DJ2LS/FreeDATA.git" }, "keywords": [ - "TNC", + "Modem", "GUI", "FreeDATA", "codec2" ], "author": "DJ2LS", - "license": "LGPL-2.1", + "license": "GPL-3.0", "bugs": { "url": "https://github.com/DJ2LS/FreeDATA/issues" }, "homepage": "https://freedata.app", "dependencies": { - "@electron/asar": "^3.2.4", - "@electron/osx-sign": "^1.0.4", + "@electron/notarize": "^2.1.0", "@popperjs/core": "^2.11.8", + "@vueuse/electron": "^10.4.1", "blob-util": "^2.0.2", "bootstrap": "^5.3.1", "bootstrap-icons": "^1.10.5", @@ -39,72 +41,42 @@ "chart.js": "^4.3.3", "chartjs-plugin-annotation": "^3.0.1", "electron-log": "^4.4.8", - "electron-updater": "^6.1.1", + "electron-updater": "^6.1.6", "emoji-picker-element": "^1.18.3", "emoji-picker-element-data": "^1.4.0", - "express-pouchdb": "^4.2.0", + "file-saver": "^2.0.5", "mime": "^3.0.0", + "pinia": "^2.1.6", "pouchdb": "^8.0.1", "pouchdb-browser": "^8.0.1", - "pouchdb-express-router": "^0.0.11", "pouchdb-find": "^8.0.1", - "pouchdb-replication": "^8.0.1", "pouchdb-upsert": "^2.2.0", "qth-locator": "^2.1.0", - "utf8": "^3.0.0", - "uuid": "^9.0.0" + "sass": "^1.66.1", + "socket.io": "^4.7.2", + "uuid": "^9.0.0", + "vue": "^3.3.4", + "vue-chartjs": "^5.2.0", + "vuemoji-picker": "^0.2.0" }, "devDependencies": { - "@electron/notarize": "^2.1.0", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@vitejs/plugin-vue": "^4.4.0", "electron": "^27.0.0", "electron-builder": "^24.6.3", - "electron-builder-notarize": "^1.5.1" - }, - "build": { - "productName": "FreeDATA", - "appId": "app.freedata", - "afterSign": "electron-builder-notarize", - "npmRebuild": "false", - "directories": { - "buildResources": "build", - "output": "dist" - }, - "mac": { - "target": [ - "default" - ], - "icon": "build/icon.png", - "hardenedRuntime": true, - "entitlements": "build/entitlements.plist", - "entitlementsInherit": "build/entitlements.plist", - "gatekeeperAssess": false - }, - "win": { - "icon": "build/icon.png", - "target": [ - "nsis" - ] - }, - "linux": { - "icon": "build/icon.png", - "target": [ - "AppImage" - ], - "category": "Development" - }, - "publish": { - "provider": "github", - "releaseType": "release" - }, - "extraResources": [ - { - "from": "../tnc/dist/tnc/", - "to": "tnc", - "filter": [ - "**/*", - "!**/.git" - ] - } - ] + "eslint": "^8.50.0", + "eslint-config-prettier": "^9.0.0", + "eslint-config-standard-with-typescript": "^39.1.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-n": "^16.1.0", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-vue": "^9.17.0", + "typescript": "^5.2.2", + "vite": "^4.3.2", + "vite-plugin-electron": "^0.14.0", + "vite-plugin-electron-renderer": "^0.14.5", + "vue": "^3.3.4", + "vue-tsc": "^1.4.2" } } diff --git a/gui/src/img/icon_cube_border.png b/gui/public/icon_cube_border.png similarity index 100% rename from gui/src/img/icon_cube_border.png rename to gui/public/icon_cube_border.png diff --git a/gui/setup.md b/gui/setup.md new file mode 100644 index 00000000..bb5f3989 --- /dev/null +++ b/gui/setup.md @@ -0,0 +1,15 @@ +https://getbootstrap.com/docs/5.3/getting-started/vite/ +https://vuejs.org/guide/essentials/event-handling.html#inline-handlers +https://linuxhint.com/install-use-bootstrap-with-vue-js/ +https://github.com/electron-vite +https://github.com/electron-vite/electron-vite-vue +https://github.com/vuejs/create-vue +https://vue-community.org/guide/ecosystem/desktop-apps.html#electron +https://blog.logrocket.com/building-app-electron-vue/ + + +Folder structure +dist-electron: Automatically compiled source from vite +electron: Source code folder for Electron stuff +public: Public data +src: VueJS source code \ No newline at end of file diff --git a/gui/sock.js b/gui/sock.js deleted file mode 100644 index 50fbb724..00000000 --- a/gui/sock.js +++ /dev/null @@ -1,939 +0,0 @@ -var net = require("net"); -const path = require("path"); -const { ipcRenderer } = require("electron"); -const FD = require("./freedata"); -const log = require("electron-log"); -const socketLog = log.scope("tnc"); -//const utf8 = require("utf8"); - -// https://stackoverflow.com/a/26227660 -var appDataFolder = - process.env.APPDATA || - (process.platform == "darwin" - ? process.env.HOME + "/Library/Application Support" - : process.env.HOME + "/.config"); -var configFolder = path.join(appDataFolder, "FreeDATA"); -var configPath = path.join(configFolder, "config.json"); -const config = require(configPath); - -var client = new net.Socket(); -var socketchunk = ""; // Current message, per connection. - -// split character -const split_char = "\0;\1;"; - -// globals for getting new data only if available so we are saving bandwidth -var rxBufferLengthTnc = 0; -var rxBufferLengthGui = 0; -//var rxMsgBufferLengthTnc = 0; -//var rxMsgBufferLengthGui = 0; - -// global to keep track of TNC connection error emissions -var tncShowConnectStateError = 1; - -// global for storing ip information -var tnc_port = config.tnc_port; -var tnc_host = config.tnc_host; - -// network connection Timeout -setTimeout(connectTNC, 2000); - -function connectTNC() { - //exports.connectTNC = function(){ - //socketLog.info('connecting to TNC...') - - //clear message buffer after reconnecting or initial connection - socketchunk = ""; - - if (config.tnclocation == "localhost") { - client.connect(3000, "127.0.0.1"); - } else { - client.connect(tnc_port, tnc_host); - } -} - -client.on("connect", function (data) { - socketLog.info("TNC connection established"); - let Data = { - busy_state: "-", - arq_state: "-", - //channel_state: "-", - frequency: "-", - mode: "-", - bandwidth: "-", - dbfs_level: 0, - }; - ipcRenderer.send("request-update-tnc-state", Data); - - // also update tnc connection state - ipcRenderer.send("request-update-tnc-connection", { - tnc_connection: client.readyState, - }); - - tncShowConnectStateError = 1; -}); - -client.on("error", function (data) { - if (tncShowConnectStateError == 1) { - socketLog.error("TNC connection error"); - tncShowConnectStateError = 0; - } - setTimeout(connectTNC, 500); - client.destroy(); - let Data = { - tnc_connection: client.readyState, - busy_state: "-", - arq_state: "-", - //channel_state: "-", - frequency: "-", - mode: "-", - bandwidth: "-", - dbfs_level: 0, - }; - ipcRenderer.send("request-update-tnc-state", Data); - ipcRenderer.send("request-update-tnc-connection", { - tnc_connection: client.readyState, - }); -}); - -/* -client.on('close', function(data) { - socketLog.info(' TNC connection closed'); - setTimeout(connectTNC, 2000) -}); -*/ - -client.on("end", function (data) { - socketLog.info("TNC connection ended"); - ipcRenderer.send("request-update-tnc-connection", { - tnc_connection: client.readyState, - }); - client.destroy(); - - setTimeout(connectTNC, 500); -}); - -writeTncCommand = function (command) { - //socketLog.info(command) - // we use the writingCommand function to update our TCPIP state because we are calling this function a lot - // if socket opened, we are able to run commands - - if (client.readyState == "open") { - client.write(command + "\n"); - } - - if (client.readyState == "closed") { - socketLog.info("CLOSED!"); - } - - if (client.readyState == "opening") { - socketLog.info("connecting to TNC..."); - } -}; - -client.on("data", function (socketdata) { - ipcRenderer.send("request-update-tnc-connection", { - tnc_connection: client.readyState, - }); - - /* - inspired by: - stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in - */ - - socketdata = socketdata.toString("utf8"); // convert data to string - socketchunk += socketdata; // append data to buffer so we can stick long data together - - // check if we received begin and end of json data - if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) { - var data = ""; - - // split data into chunks if we received multiple commands - socketchunk = socketchunk.split("\n"); - //don't think this is needed anymore - //data = JSON.parse(socketchunk[0]) - - // search for empty entries in socketchunk and remove them - for (i = 0; i < socketchunk.length; i++) { - if (socketchunk[i] === "") { - socketchunk.splice(i, 1); - } - } - - //iterate through socketchunks array to execute multiple commands in row - for (i = 0; i < socketchunk.length; i++) { - //check if data is not empty - if (socketchunk[i].length > 0) { - //try to parse JSON - try { - data = JSON.parse(socketchunk[i]); - } catch (e) { - socketLog.info("Throwing away data!!!!\n" + e); // "SyntaxError - //socketLog.info(e); // "SyntaxError - socketLog.info(socketchunk[i]); - socketchunk = ""; - //If we're here, I don't think we want to process any data that may be in data variable - continue; - } - } - - if (data["command"] == "tnc_state") { - //socketLog.info(data) - // set length of RX Buffer to global variable - rxBufferLengthTnc = data["rx_buffer_length"]; - //rxMsgBufferLengthTnc = data["rx_msg_buffer_length"]; - - let Data = { - mycallsign: data["mycallsign"], - mygrid: data["mygrid"], - ptt_state: data["ptt_state"], - busy_state: data["tnc_state"], - arq_state: data["arq_state"], - arq_session: data["arq_session"], - //channel_state: data['CHANNEL_STATE'], - frequency: data["frequency"], - speed_level: data["speed_level"], - mode: data["mode"], - bandwidth: data["bandwidth"], - dbfs_level: data["audio_dbfs"], - fft: data["fft"], - channel_busy: data["channel_busy"], - channel_busy_slot: data["channel_busy_slot"], - scatter: data["scatter"], - info: data["info"], - rx_buffer_length: data["rx_buffer_length"], - rx_msg_buffer_length: data["rx_msg_buffer_length"], - tx_n_max_retries: data["tx_n_max_retries"], - arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"], - arq_tx_n_bursts: data["arq_tx_n_bursts"], - arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"], - arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"], - arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"], - arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"], - arq_n_arq_frames_per_data_frame: - data["arq_n_arq_frames_per_data_frame"], - arq_bytes_per_minute: data["arq_bytes_per_minute"], - arq_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"], - routing_table: data["routing_table"], - mesh_signalling_table: data["mesh_signalling_table"], - beacon_state: data["beacon_state"], - hamlib_status: data["hamlib_status"], - listen: data["listen"], - audio_recording: data["audio_recording"], - speed_list: data["speed_list"], - strength: data["strength"], - is_codec2_traffic: data["is_codec2_traffic"], - //speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}], - }; - - ipcRenderer.send("request-update-tnc-state", Data); - //continue to next for loop iteration, nothing else needs to be done here - continue; - } - - // ----------- catch tnc messages START ----------- - if (data["freedata"] == "tnc-message") { - switch (data["fec"]) { - case "is_writing": - // RX'd FECiswriting - ipcRenderer.send("request-show-fec-toast-iswriting", { - data: [data], - }); - break; - - case "broadcast": - // RX'd FEC BROADCAST - var encoded_data = FD.atob_FD(data["data"]); - var splitted_data = encoded_data.split(split_char); - var messageArray = []; - if (splitted_data[0] == "m") { - messageArray.push(data); - console.log(data); - } - - let Messages = { - data: messageArray, - }; - ipcRenderer.send("request-new-msg-received", Messages); - break; - } - - switch (data["cq"]) { - case "transmitting": - // CQ TRANSMITTING - ipcRenderer.send("request-show-cq-toast-transmitting", { - data: [data], - }); - break; - - case "received": - // CQ RECEIVED - ipcRenderer.send("request-show-cq-toast-received", { - data: [data], - }); - break; - } - - switch (data["qrv"]) { - case "transmitting": - // QRV TRANSMITTING - ipcRenderer.send("request-show-qrv-toast-transmitting", { - data: [data], - }); - break; - - case "received": - // QRV RECEIVED - ipcRenderer.send("request-show-qrv-toast-received", { - data: [data], - }); - break; - } - - switch (data["beacon"]) { - case "transmitting": - // BEACON TRANSMITTING - ipcRenderer.send("request-show-beacon-toast-transmitting", { - data: [data], - }); - break; - - case "received": - // BEACON RECEIVED - ipcRenderer.send("request-show-beacon-toast-received", { - data: [data], - }); - ipcRenderer.send("request-new-msg-received", { data: [data] }); - break; - } - - switch (data["ping"]) { - case "transmitting": - // PING TRANSMITTING - ipcRenderer.send("request-show-ping-toast-transmitting", { - data: [data], - }); - break; - - case "received": - // PING RECEIVED - ipcRenderer.send("request-show-ping-toast-received", { - data: [data], - }); - ipcRenderer.send("request-new-msg-received", { data: [data] }); - break; - - case "acknowledge": - // PING ACKNOWLEDGE - ipcRenderer.send("request-show-ping-toast-received-ack", { - data: [data], - }); - ipcRenderer.send("request-new-msg-received", { data: [data] }); - break; - } - - // ARQ SESSION && freedata == tnc-message - if (data["arq"] == "session") { - switch (data["status"]) { - case "connecting": - // ARQ Open - ipcRenderer.send("request-show-arq-toast-session-connecting", { - data: [data], - }); - break; - - case "connected": - // ARQ Opening - ipcRenderer.send("request-show-arq-toast-session-connected", { - data: [data], - }); - break; - - case "waiting": - // ARQ Opening - ipcRenderer.send("request-show-arq-toast-session-waiting", { - data: [data], - }); - break; - - case "close": - // ARQ Closing - ipcRenderer.send("request-show-arq-toast-session-close", { - data: [data], - }); - break; - - case "failed": - // ARQ Failed - ipcRenderer.send("request-show-arq-toast-session-failed", { - data: [data], - }); - break; - } - } - // ARQ TRANSMISSION && freedata == tnc-message - if (data["arq"] == "transmission") { - switch (data["status"]) { - case "opened": - // ARQ Open - ipcRenderer.send("request-show-arq-toast-datachannel-opened", { - data: [data], - }); - break; - - case "opening": - // ARQ Opening IRS/ISS - if (data["irs"] == "False") { - ipcRenderer.send("request-show-arq-toast-datachannel-opening", { - data: [data], - }); - ipcRenderer.send("request-update-transmission-status", { - data: [data], - }); - } else { - ipcRenderer.send( - "request-show-arq-toast-datachannel-received-opener", - { data: [data] }, - ); - ipcRenderer.send("request-update-reception-status", { - data: [data], - }); - } - - break; - - case "waiting": - // ARQ waiting - ipcRenderer.send("request-show-arq-toast-datachannel-waiting", { - data: [data], - }); - break; - - case "receiving": - // ARQ RX - ipcRenderer.send("request-update-reception-status", { - data: [data], - }); - break; - - case "failed": - // ARQ TX Failed - if (data["reason"] == "protocol version missmatch") { - ipcRenderer.send( - "request-show-arq-toast-transmission-failed-ver", - { data: [data] }, - ); - } else { - ipcRenderer.send("request-show-arq-toast-transmission-failed", { - data: [data], - }); - } - switch (data["irs"]) { - case "True": - ipcRenderer.send("request-update-reception-status", { - data: [data], - }); - break; - default: - ipcRenderer.send("request-update-transmission-status", { - data: [data], - }); - break; - } - break; - - case "received": - // ARQ Received - ipcRenderer.send("request-show-arq-toast-transmission-received", { - data: [data], - }); - - ipcRenderer.send("request-update-reception-status", { - data: [data], - }); - - dataArray = []; - messageArray = []; - - socketLog.info(data); - // we need to encode here to do a deep check for checking if file or message - //var encoded_data = atob(data['data']) - var encoded_data = FD.atob_FD(data["data"]); - var splitted_data = encoded_data.split(split_char); - - if (splitted_data[0] == "f") { - dataArray.push(data); - } - - if (splitted_data[0] == "m") { - messageArray.push(data); - console.log(data); - } - - rxBufferLengthGui = dataArray.length; - let Files = { - data: dataArray, - }; - ipcRenderer.send("request-update-rx-buffer", Files); - ipcRenderer.send("request-new-msg-received", Files); - - //rxMsgBufferLengthGui = messageArray.length; - let Messages = { - data: messageArray, - }; - ipcRenderer.send("request-new-msg-received", Messages); - break; - - case "transmitting": - // ARQ transmitting - ipcRenderer.send( - "request-show-arq-toast-transmission-transmitting", - { data: [data] }, - ); - ipcRenderer.send("request-update-transmission-status", { - data: [data], - }); - break; - - case "transmitted": - // ARQ transmitted - ipcRenderer.send( - "request-show-arq-toast-transmission-transmitted", - { data: [data] }, - ); - ipcRenderer.send("request-update-transmission-status", { - data: [data], - }); - break; - } - } - } - - // ----------- catch tnc info messages END ----------- - - // if we manually checking for the rx buffer we are getting an array of multiple data - if (data["command"] == "rx_buffer") { - socketLog.info(data); - // iterate through buffer list and sort it to file or message array - dataArray = []; - messageArray = []; - - for (i = 0; i < data["data-array"].length; i++) { - try { - // we need to encode here to do a deep check for checking if file or message - //var encoded_data = atob(data['data-array'][i]['data']) - var encoded_data = FD.atob_FD(data["data-array"][i]["data"]); - var splitted_data = encoded_data.split(split_char); - - if (splitted_data[0] == "f") { - dataArray.push(data["data-array"][i]); - } - - if (splitted_data[0] == "m") { - messageArray.push(data["data-array"][i]); - } - } catch (e) { - socketLog.info(e); - } - } - - rxBufferLengthGui = dataArray.length; - let Files = { - data: dataArray, - }; - ipcRenderer.send("request-update-rx-buffer", Files); - - //rxMsgBufferLengthGui = messageArray.length; - let Messages = { - data: messageArray, - }; - //ipcRenderer.send('request-update-rx-msg-buffer', Messages); - ipcRenderer.send("request-new-msg-received", Messages); - } - } - - //finally delete message buffer - socketchunk = ""; - } -}); - -function hexToBytes(hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) - bytes.push(parseInt(hex.substr(c, 2), 16)); - return bytes; -} - -//Get TNC State -exports.getTncState = function () { - command = '{"type" : "get", "command" : "tnc_state"}'; - writeTncCommand(command); -}; - -//Get DATA State -exports.getDataState = function () { - command = '{"type" : "get", "command" : "data_state"}'; - //writeTncCommand(command) -}; - -// Send Ping -exports.sendPing = function (dxcallsign) { - command = - '{"type" : "ping", "command" : "ping", "dxcallsign" : "' + - dxcallsign + - '"}'; - writeTncCommand(command); -}; - -// Send Mesh Ping -exports.sendMeshPing = function (dxcallsign) { - command = - '{"type" : "mesh", "command" : "ping", "dxcallsign" : "' + - dxcallsign + - '"}'; - writeTncCommand(command); -}; - -// Send CQ -exports.sendCQ = function () { - command = '{"type" : "broadcast", "command" : "cqcqcq"}'; - writeTncCommand(command); -}; - -// Set AUDIO Level -exports.setTxAudioLevel = function (value) { - command = - '{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}'; - writeTncCommand(command); -}; - -// Send File -exports.sendFile = function ( - dxcallsign, - mode, - frames, - filename, - filetype, - data, - checksum, -) { - socketLog.info(data); - socketLog.info(filetype); - socketLog.info(filename); - - var datatype = "f"; - - data = - datatype + - split_char + - filename + - split_char + - filetype + - split_char + - checksum + - split_char + - data; - socketLog.info(data); - //socketLog.info(btoa(data)) - //Btoa / atob will not work with charsets > 8 bits (i.e. the emojis); should probably move away from using it - //TODO: Will need to update anyother occurences and throughly test - //data = btoa(data) - data = FD.btoa_FD(data); - - command = - '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + - dxcallsign + - '", "mode" : "' + - mode + - '", "n_frames" : "' + - frames + - '", "data" : "' + - data + - '"}]}'; - writeTncCommand(command); -}; - -// Send Message -exports.sendMessage = function ( - dxcallsign, - mode, - frames, - data, - checksum, - uuid, - command, -) { - data = FD.btoa_FD( - "m" + - split_char + - command + - split_char + - checksum + - split_char + - uuid + - split_char + - data, - ); - command = - '{"type" : "arq", "command" : "send_raw", "uuid" : "' + - uuid + - '", "parameter" : [{"dxcallsign" : "' + - dxcallsign + - '", "mode" : "' + - mode + - '", "n_frames" : "' + - frames + - '", "data" : "' + - data + - '", "attempts": "10"}]}'; - socketLog.info(command); - socketLog.info("-------------------------------------"); - writeTncCommand(command); -}; - -// Send Request message -//It would be then „m + split + request + split + request-type“ -function sendRequest(dxcallsign, mode, frames, data, command) { - data = FD.btoa_FD("m" + split_char + command + split_char + data); - command = - '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + - dxcallsign + - '", "mode" : "' + - mode + - '", "n_frames" : "' + - frames + - '", "data" : "' + - data + - '", "attempts": "10"}]}'; - socketLog.info(command); - socketLog.info("--------------REQ--------------------"); - writeTncCommand(command); -} - -// Send Response message -//It would be then „m + split + request + split + request-type“ -function sendResponse(dxcallsign, mode, frames, data, command) { - data = FD.btoa_FD("m" + split_char + command + split_char + data); - command = - '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + - dxcallsign + - '", "mode" : "' + - mode + - '", "n_frames" : "' + - frames + - '", "data" : "' + - data + - '", "attempts": "10"}]}'; - socketLog.info(command); - socketLog.info("--------------RES--------------------"); - writeTncCommand(command); -} - -//Send station info request -exports.sendRequestInfo = function (dxcallsign) { - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendRequest(dxcallsign, 255, 1, "0", "req"); -}; - -//Send shared folder file list request -exports.sendRequestSharedFolderList = function (dxcallsign) { - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendRequest(dxcallsign, 255, 1, "1", "req"); -}; - -//Send shared file request -exports.sendRequestSharedFile = function (dxcallsign, file) { - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendRequest(dxcallsign, 255, 1, "2" + file, "req"); -}; - -//Send station info response -exports.sendResponseInfo = function (dxcallsign, userinfo) { - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendResponse(dxcallsign, 255, 1, userinfo, "res-0"); -}; - -//Send shared folder response -exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) { - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1"); -}; - -//Send shared file response -exports.sendResponseSharedFile = function ( - dxcallsign, - sharedFile, - sharedFileData, -) { - console.log( - "In sendResponseSharedFile", - dxcallsign, - sharedFile, - sharedFileData, - ); - //Command 0 = user/station information - //Command 1 = shared folder list - //Command 2 = shared file transfer - sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2"); -}; - -//STOP TRANSMISSION -exports.stopTransmission = function () { - command = '{"type" : "arq", "command": "stop_transmission"}'; - writeTncCommand(command); -}; - -// Get RX BUffer -exports.getRxBuffer = function () { - command = '{"type" : "get", "command" : "rx_buffer"}'; - - // call command only if new data arrived - if (rxBufferLengthGui != rxBufferLengthTnc) { - writeTncCommand(command); - } -}; - -// START BEACON -exports.startBeacon = function (interval) { - command = - '{"type" : "broadcast", "command" : "start_beacon", "parameter": "' + - interval + - '"}'; - writeTncCommand(command); -}; - -// STOP BEACON -exports.stopBeacon = function () { - command = '{"type" : "broadcast", "command" : "stop_beacon"}'; - writeTncCommand(command); -}; - -// OPEN ARQ SESSION -exports.connectARQ = function (dxcallsign) { - command = - '{"type" : "arq", "command" : "connect", "dxcallsign": "' + - dxcallsign + - '", "attempts": "10"}'; - writeTncCommand(command); -}; - -// CLOSE ARQ SESSION -exports.disconnectARQ = function () { - command = '{"type" : "arq", "command" : "disconnect"}'; - writeTncCommand(command); -}; - -// SEND TEST FRAME -exports.sendTestFrame = function () { - command = '{"type" : "set", "command" : "send_test_frame"}'; - writeTncCommand(command); -}; - -// SEND FEC -exports.sendFEC = function (mode, payload) { - command = - '{"type" : "fec", "command" : "transmit", "mode" : "' + - mode + - '", "payload" : "' + - payload + - '"}'; - writeTncCommand(command); -}; - -// SEND FEC IS WRITING -exports.sendFecIsWriting = function (mycallsign) { - command = - '{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' + - mycallsign + - '"}'; - writeTncCommand(command); -}; - -// SEND FEC TO BROADCASTCHANNEL -exports.sendBroadcastChannel = function (channel, data_out, uuid) { - let checksum = ""; - let command = ""; - let data = FD.btoa_FD( - "m" + - split_char + - channel + - //split_char + - //checksum + - split_char + - uuid + - split_char + - data_out, - ); - console.log(data.length); - let payload = data; - command = - '{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' + - payload + - '"}'; - writeTncCommand(command); -}; - -// RECORD AUDIO -exports.record_audio = function () { - command = '{"type" : "set", "command" : "record_audio"}'; - writeTncCommand(command); -}; - -// SET FREQUENCY -exports.set_frequency = function (frequency) { - command = - '{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}"; - writeTncCommand(command); -}; - -// SET MODE -exports.set_mode = function (mode) { - command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}'; - console.log(command); - writeTncCommand(command); -}; - -ipcRenderer.on("action-update-tnc-ip", (event, arg) => { - client.destroy(); - let Data = { - busy_state: "-", - arq_state: "-", - //channel_state: "-", - frequency: "-", - mode: "-", - bandwidth: "-", - dbfs_level: 0, - }; - ipcRenderer.send("request-update-tnc-state", Data); - tnc_port = arg.port; - tnc_host = arg.adress; - connectTNC(); -}); - -// https://stackoverflow.com/a/50579690 -// crc32 calculation -//console.log(crc32('abc')); -//console.log(crc32('abc').toString(16).toUpperCase()); // hex -var crc32 = function (r) { - for (var a, o = [], c = 0; c < 256; c++) { - a = c; - for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1; - o[c] = a; - } - for (var n = -1, t = 0; t < r.length; t++) - n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))]; - return (-1 ^ n) >>> 0; -}; diff --git a/gui/src/App.vue b/gui/src/App.vue new file mode 100644 index 00000000..f06de569 --- /dev/null +++ b/gui/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/gui/src/waterfall/LICENSE b/gui/src/assets/waterfall/LICENSE similarity index 100% rename from gui/src/waterfall/LICENSE rename to gui/src/assets/waterfall/LICENSE diff --git a/gui/src/waterfall/README.rst b/gui/src/assets/waterfall/README.rst similarity index 100% rename from gui/src/waterfall/README.rst rename to gui/src/assets/waterfall/README.rst diff --git a/gui/src/waterfall/colormap.js b/gui/src/assets/waterfall/colormap.js similarity index 100% rename from gui/src/waterfall/colormap.js rename to gui/src/assets/waterfall/colormap.js diff --git a/gui/src/waterfall/index.html b/gui/src/assets/waterfall/index.html similarity index 100% rename from gui/src/waterfall/index.html rename to gui/src/assets/waterfall/index.html diff --git a/gui/src/waterfall/make_colormap.py b/gui/src/assets/waterfall/make_colormap.py similarity index 100% rename from gui/src/waterfall/make_colormap.py rename to gui/src/assets/waterfall/make_colormap.py diff --git a/gui/src/waterfall/script.js b/gui/src/assets/waterfall/script.js similarity index 100% rename from gui/src/waterfall/script.js rename to gui/src/assets/waterfall/script.js diff --git a/gui/src/assets/waterfall/spectrum.js b/gui/src/assets/waterfall/spectrum.js new file mode 100644 index 00000000..df217091 --- /dev/null +++ b/gui/src/assets/waterfall/spectrum.js @@ -0,0 +1,2310 @@ +/* + * Copyright (c) 2019 Jeppe Ledet-Pedersen + * This software is released under the MIT license. + * See the LICENSE file for further details. + */ + +"use strict"; + +Spectrum.prototype.squeeze = function (value, out_min, out_max) { + if (value <= this.min_db) return out_min; + else if (value >= this.max_db) return out_max; + else + return Math.round( + ((value - this.min_db) / (this.max_db - this.min_db)) * out_max + ); +}; + +Spectrum.prototype.rowToImageData = function (bins) { + for (var i = 0; i < this.imagedata.data.length; i += 4) { + var cindex = this.squeeze(bins[i / 4], 0, 255); + var color = this.colormap[cindex]; + this.imagedata.data[i + 0] = color[0]; + this.imagedata.data[i + 1] = color[1]; + this.imagedata.data[i + 2] = color[2]; + this.imagedata.data[i + 3] = 255; + } +}; + +Spectrum.prototype.addWaterfallRow = function (bins) { + // Shift waterfall 1 row down + this.ctx_wf.drawImage( + this.ctx_wf.canvas, + 0, + 0, + this.wf_size, + this.wf_rows - 1, + 0, + 1, + this.wf_size, + this.wf_rows - 1 + ); + + // Draw new line on waterfall canvas + this.rowToImageData(bins); + this.ctx_wf.putImageData(this.imagedata, 0, 0); + + var width = this.ctx.canvas.width; + var height = this.ctx.canvas.height; + + // Copy scaled FFT canvas to screen. Only copy the number of rows that will + // fit in waterfall area to avoid vertical scaling. + this.ctx.imageSmoothingEnabled = false; + var rows = Math.min(this.wf_rows, height - this.spectrumHeight); + this.ctx.drawImage( + this.ctx_wf.canvas, + 0, + 0, + this.wf_size, + rows, + 0, + this.spectrumHeight, + width, + height - this.spectrumHeight + ); +}; + +Spectrum.prototype.drawFFT = function (bins) { + this.ctx.beginPath(); + this.ctx.moveTo(-1, this.spectrumHeight + 1); + for (var i = 0; i < bins.length; i++) { + var y = this.spectrumHeight - this.squeeze(bins[i], 0, this.spectrumHeight); + if (y > this.spectrumHeight - 1) y = this.spectrumHeight + 1; // Hide underflow + if (y < 0) y = 0; + if (i == 0) this.ctx.lineTo(-1, y); + this.ctx.lineTo(i, y); + if (i == bins.length - 1) this.ctx.lineTo(this.wf_size + 1, y); + } + this.ctx.lineTo(this.wf_size + 1, this.spectrumHeight + 1); + this.ctx.strokeStyle = "#fefefe"; + this.ctx.stroke(); +}; + +//Spectrum.prototype.drawSpectrum = function(bins) { +Spectrum.prototype.drawSpectrum = function () { + var width = this.ctx.canvas.width; + var height = this.ctx.canvas.height; + + // Modification by DJ2LS + // Draw bandwidth lines + // TODO: Math not correct. But a first attempt + // it seems position is more or less equal to frequenzy by factor 10 + // eg. position 150 == 1500Hz + /* + // CENTER LINE + this.ctx_wf.beginPath(); + this.ctx_wf.moveTo(150,0); + this.ctx_wf.lineTo(150, height); + this.ctx_wf.lineWidth = 1; + this.ctx_wf.strokeStyle = '#8C8C8C'; + this.ctx_wf.stroke() + */ + + // 586Hz and 1700Hz LINES + var linePositionLow = 121.6; //150 - bandwith/20 + var linePositionHigh = 178.4; //150 + bandwidth/20 + var linePositionLow2 = 65; //150 - bandwith/20 + var linePositionHigh2 = 235; //150 + bandwith/20 + this.ctx_wf.beginPath(); + this.ctx_wf.moveTo(linePositionLow, 0); + this.ctx_wf.lineTo(linePositionLow, height); + this.ctx_wf.moveTo(linePositionHigh, 0); + this.ctx_wf.lineTo(linePositionHigh, height); + this.ctx_wf.moveTo(linePositionLow2, 0); + this.ctx_wf.lineTo(linePositionLow2, height); + this.ctx_wf.moveTo(linePositionHigh2, 0); + this.ctx_wf.lineTo(linePositionHigh2, height); + this.ctx_wf.lineWidth = 1; + this.ctx_wf.strokeStyle = "#C3C3C3"; + this.ctx_wf.stroke(); + + // ---- END OF MODIFICATION ------ + + // Fill with black + this.ctx.fillStyle = "white"; + this.ctx.fillRect(0, 0, width, height); + + //Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements + return; + /* + // FFT averaging + if (this.averaging > 0) { + if (!this.binsAverage || this.binsAverage.length != bins.length) { + this.binsAverage = Array.from(bins); + } else { + for (var i = 0; i < bins.length; i++) { + this.binsAverage[i] += this.alpha * (bins[i] - this.binsAverage[i]); + } + } + bins = this.binsAverage; + } + + // Max hold + if (this.maxHold) { + if (!this.binsMax || this.binsMax.length != bins.length) { + this.binsMax = Array.from(bins); + } else { + for (var i = 0; i < bins.length; i++) { + if (bins[i] > this.binsMax[i]) { + this.binsMax[i] = bins[i]; + } else { + // Decay + this.binsMax[i] = 1.0025 * this.binsMax[i]; + } + } + } + } + + // Do not draw anything if spectrum is not visible + if (this.ctx_axes.canvas.height < 1) + return; + + // Scale for FFT + this.ctx.save(); + this.ctx.scale(width / this.wf_size, 1); + + // Draw maxhold + if (this.maxHold) + this.drawFFT(this.binsMax); + + // Draw FFT bins + this.drawFFT(bins); + + // Restore scale + this.ctx.restore(); + + // Fill scaled path + this.ctx.fillStyle = this.gradient; + this.ctx.fill(); + + // 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 () { + var width = this.ctx_axes.canvas.width; + var height = this.ctx_axes.canvas.height; + + // Clear axes canvas + this.ctx_axes.clearRect(0, 0, width, height); + + // Draw axes + this.ctx_axes.font = "12px sans-serif"; + this.ctx_axes.fillStyle = "white"; + this.ctx_axes.textBaseline = "middle"; + + this.ctx_axes.textAlign = "left"; + var step = 10; + for (var i = this.min_db + 10; i <= this.max_db - 10; i += step) { + var y = height - this.squeeze(i, 0, height); + this.ctx_axes.fillText(i, 5, y); + + this.ctx_axes.beginPath(); + this.ctx_axes.moveTo(20, y); + this.ctx_axes.lineTo(width, y); + this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)"; + this.ctx_axes.stroke(); + } + + this.ctx_axes.textBaseline = "bottom"; + for (var i = 0; i < 11; i++) { + var x = Math.round(width / 10) * i; + + if (this.spanHz > 0) { + var adjust = 0; + if (i == 0) { + this.ctx_axes.textAlign = "left"; + adjust = 3; + } else if (i == 10) { + this.ctx_axes.textAlign = "right"; + adjust = -3; + } else { + this.ctx_axes.textAlign = "center"; + } + + var freq = this.centerHz + (this.spanHz / 10) * (i - 5); + if (this.centerHz + this.spanHz > 1e6) freq = freq / 1e6 + "M"; + else if (this.centerHz + this.spanHz > 1e3) freq = freq / 1e3 + "k"; + this.ctx_axes.fillText(freq, x + adjust, height - 3); + } + + this.ctx_axes.beginPath(); + this.ctx_axes.moveTo(x, 0); + this.ctx_axes.lineTo(x, height); + this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)"; + this.ctx_axes.stroke(); + } +}; + +Spectrum.prototype.addData = function (data) { + if (!this.paused) { + if (data.length != this.wf_size) { + this.wf_size = data.length; + this.ctx_wf.canvas.width = data.length; + this.ctx_wf.fillStyle = "white"; + 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(); + this.addWaterfallRow(data); + this.resize(); + } +}; + +Spectrum.prototype.updateSpectrumRatio = function () { + this.spectrumHeight = Math.round( + (this.canvas.height * this.spectrumPercent) / 100.0 + ); + + this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight); + for (var i = 0; i < this.colormap.length; i++) { + var c = this.colormap[this.colormap.length - 1 - i]; + this.gradient.addColorStop( + i / this.colormap.length, + "rgba(" + c[0] + "," + c[1] + "," + c[2] + ", 1.0)" + ); + } +}; + +Spectrum.prototype.resize = function () { + var width = this.canvas.clientWidth; + var height = this.canvas.clientHeight; + // little helper for setting height of clientHeight is not working as expected + if (height == 0){ + var height = 250 + + } + + + if (this.canvas.width != width || this.canvas.height != height) { + this.canvas.width = width; + this.canvas.height = height; + this.updateSpectrumRatio(); + } + + if (this.axes.width != width || this.axes.height != this.spectrumHeight) { + this.axes.width = width; + this.axes.height = this.spectrumHeight; + this.updateAxes(); + } +}; + +Spectrum.prototype.setSpectrumPercent = function (percent) { + if (percent >= 0 && percent <= 100) { + this.spectrumPercent = percent; + this.updateSpectrumRatio(); + } +}; + +Spectrum.prototype.incrementSpectrumPercent = function () { + if (this.spectrumPercent + this.spectrumPercentStep <= 100) { + this.setSpectrumPercent(this.spectrumPercent + this.spectrumPercentStep); + } +}; + +Spectrum.prototype.decrementSpectrumPercent = function () { + if (this.spectrumPercent - this.spectrumPercentStep >= 0) { + this.setSpectrumPercent(this.spectrumPercent - this.spectrumPercentStep); + } +}; + +Spectrum.prototype.toggleColor = function () { + this.colorindex++; + if (this.colorindex >= colormaps.length) this.colorindex = 0; + this.colormap = colormaps[this.colorindex]; + this.updateSpectrumRatio(); +}; + +Spectrum.prototype.setRange = function (min_db, max_db) { + this.min_db = min_db; + this.max_db = max_db; + this.updateAxes(); +}; + +Spectrum.prototype.rangeUp = function () { + this.setRange(this.min_db - 5, this.max_db - 5); +}; + +Spectrum.prototype.rangeDown = function () { + this.setRange(this.min_db + 5, this.max_db + 5); +}; + +Spectrum.prototype.rangeIncrease = function () { + this.setRange(this.min_db - 5, this.max_db + 5); +}; + +Spectrum.prototype.rangeDecrease = function () { + if (this.max_db - this.min_db > 10) + this.setRange(this.min_db + 5, this.max_db - 5); +}; + +Spectrum.prototype.setCenterHz = function (hz) { + this.centerHz = hz; + this.updateAxes(); +}; + +Spectrum.prototype.setSpanHz = function (hz) { + this.spanHz = hz; + this.updateAxes(); +}; + +Spectrum.prototype.setAveraging = function (num) { + if (num >= 0) { + this.averaging = num; + this.alpha = 2 / (this.averaging + 1); + } +}; + +Spectrum.prototype.incrementAveraging = function () { + this.setAveraging(this.averaging + 1); +}; + +Spectrum.prototype.decrementAveraging = function () { + if (this.averaging > 0) { + this.setAveraging(this.averaging - 1); + } +}; + +Spectrum.prototype.setPaused = function (paused) { + this.paused = paused; +}; + +Spectrum.prototype.togglePaused = function () { + this.setPaused(!this.paused); +}; + +Spectrum.prototype.setMaxHold = function (maxhold) { + this.maxHold = maxhold; + this.binsMax = undefined; +}; + +Spectrum.prototype.toggleMaxHold = function () { + this.setMaxHold(!this.maxHold); +}; + +Spectrum.prototype.toggleFullscreen = function () { + if (!this.fullscreen) { + if (this.canvas.requestFullscreen) { + this.canvas.requestFullscreen(); + } else if (this.canvas.mozRequestFullScreen) { + this.canvas.mozRequestFullScreen(); + } else if (this.canvas.webkitRequestFullscreen) { + this.canvas.webkitRequestFullscreen(); + } else if (this.canvas.msRequestFullscreen) { + this.canvas.msRequestFullscreen(); + } + this.fullscreen = true; + } else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + this.fullscreen = false; + } +}; + +Spectrum.prototype.onKeypress = function (e) { + if (e.key == " ") { + this.togglePaused(); + } else if (e.key == "f") { + this.toggleFullscreen(); + } else if (e.key == "c") { + this.toggleColor(); + } else if (e.key == "ArrowUp") { + this.rangeUp(); + } else if (e.key == "ArrowDown") { + this.rangeDown(); + } else if (e.key == "ArrowLeft") { + this.rangeDecrease(); + } else if (e.key == "ArrowRight") { + this.rangeIncrease(); + } else if (e.key == "s") { + this.incrementSpectrumPercent(); + } else if (e.key == "w") { + this.decrementSpectrumPercent(); + } else if (e.key == "+") { + this.incrementAveraging(); + } else if (e.key == "-") { + this.decrementAveraging(); + } else if (e.key == "m") { + this.toggleMaxHold(); + } +}; + +export function Spectrum(id, options) { + + console.log("waterfall init....") + console.log(document.getElementById(id)) + + // Handle options + this.centerHz = options && options.centerHz ? options.centerHz : 1500; + this.spanHz = options && options.spanHz ? options.spanHz : 0; + this.wf_size = options && options.wf_size ? options.wf_size : 0; + this.wf_rows = options && options.wf_rows ? options.wf_rows : 1024; + this.spectrumPercent = + options && options.spectrumPercent ? options.spectrumPercent : 0; + this.spectrumPercentStep = + options && options.spectrumPercentStep ? options.spectrumPercentStep : 0; + this.averaging = options && options.averaging ? options.averaging : 0; + this.maxHold = options && options.maxHold ? options.maxHold : false; + + // Setup state + this.paused = false; + this.fullscreen = false; + this.min_db = 0; + this.max_db = 70; + this.spectrumHeight = 0; + + // Colors + this.colorindex = 0; + this.colormap = colormaps[1]; + + // Create main canvas and adjust dimensions to match actual + this.canvas = document.getElementById(id); + + this.canvas.height = this.canvas.clientHeight; + this.canvas.width = this.canvas.clientWidth; + + this.ctx = this.canvas.getContext("2d"); + this.ctx.fillStyle = "white"; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + // Create offscreen canvas for axes + this.axes = document.createElement("canvas"); + this.axes.height = 1; // Updated later + this.axes.width = this.canvas.width; + this.ctx_axes = this.axes.getContext("2d"); + + // Create offscreen canvas for waterfall + this.wf = document.createElement("canvas"); + this.wf.height = this.wf_rows; + this.wf.width = this.wf_size; + this.ctx_wf = this.wf.getContext("2d"); + + // Trigger first render + this.setAveraging(this.averaging); + this.updateSpectrumRatio(); + this.resize(); +} + + + +var turbo = [ + [48, 18, 59], + [50, 21, 67], + [51, 24, 74], + [52, 27, 81], + [53, 30, 88], + [54, 33, 95], + [55, 36, 102], + [56, 39, 109], + [57, 42, 115], + [58, 45, 121], + [59, 47, 128], + [60, 50, 134], + [61, 53, 139], + [62, 56, 145], + [63, 59, 151], + [63, 62, 156], + [64, 64, 162], + [65, 67, 167], + [65, 70, 172], + [66, 73, 177], + [66, 75, 181], + [67, 78, 186], + [68, 81, 191], + [68, 84, 195], + [68, 86, 199], + [69, 89, 203], + [69, 92, 207], + [69, 94, 211], + [70, 97, 214], + [70, 100, 218], + [70, 102, 221], + [70, 105, 224], + [70, 107, 227], + [71, 110, 230], + [71, 113, 233], + [71, 115, 235], + [71, 118, 238], + [71, 120, 240], + [71, 123, 242], + [70, 125, 244], + [70, 128, 246], + [70, 130, 248], + [70, 133, 250], + [70, 135, 251], + [69, 138, 252], + [69, 140, 253], + [68, 143, 254], + [67, 145, 254], + [66, 148, 255], + [65, 150, 255], + [64, 153, 255], + [62, 155, 254], + [61, 158, 254], + [59, 160, 253], + [58, 163, 252], + [56, 165, 251], + [55, 168, 250], + [53, 171, 248], + [51, 173, 247], + [49, 175, 245], + [47, 178, 244], + [46, 180, 242], + [44, 183, 240], + [42, 185, 238], + [40, 188, 235], + [39, 190, 233], + [37, 192, 231], + [35, 195, 228], + [34, 197, 226], + [32, 199, 223], + [31, 201, 221], + [30, 203, 218], + [28, 205, 216], + [27, 208, 213], + [26, 210, 210], + [26, 212, 208], + [25, 213, 205], + [24, 215, 202], + [24, 217, 200], + [24, 219, 197], + [24, 221, 194], + [24, 222, 192], + [24, 224, 189], + [25, 226, 187], + [25, 227, 185], + [26, 228, 182], + [28, 230, 180], + [29, 231, 178], + [31, 233, 175], + [32, 234, 172], + [34, 235, 170], + [37, 236, 167], + [39, 238, 164], + [42, 239, 161], + [44, 240, 158], + [47, 241, 155], + [50, 242, 152], + [53, 243, 148], + [56, 244, 145], + [60, 245, 142], + [63, 246, 138], + [67, 247, 135], + [70, 248, 132], + [74, 248, 128], + [78, 249, 125], + [82, 250, 122], + [85, 250, 118], + [89, 251, 115], + [93, 252, 111], + [97, 252, 108], + [101, 253, 105], + [105, 253, 102], + [109, 254, 98], + [113, 254, 95], + [117, 254, 92], + [121, 254, 89], + [125, 255, 86], + [128, 255, 83], + [132, 255, 81], + [136, 255, 78], + [139, 255, 75], + [143, 255, 73], + [146, 255, 71], + [150, 254, 68], + [153, 254, 66], + [156, 254, 64], + [159, 253, 63], + [161, 253, 61], + [164, 252, 60], + [167, 252, 58], + [169, 251, 57], + [172, 251, 56], + [175, 250, 55], + [177, 249, 54], + [180, 248, 54], + [183, 247, 53], + [185, 246, 53], + [188, 245, 52], + [190, 244, 52], + [193, 243, 52], + [195, 241, 52], + [198, 240, 52], + [200, 239, 52], + [203, 237, 52], + [205, 236, 52], + [208, 234, 52], + [210, 233, 53], + [212, 231, 53], + [215, 229, 53], + [217, 228, 54], + [219, 226, 54], + [221, 224, 55], + [223, 223, 55], + [225, 221, 55], + [227, 219, 56], + [229, 217, 56], + [231, 215, 57], + [233, 213, 57], + [235, 211, 57], + [236, 209, 58], + [238, 207, 58], + [239, 205, 58], + [241, 203, 58], + [242, 201, 58], + [244, 199, 58], + [245, 197, 58], + [246, 195, 58], + [247, 193, 58], + [248, 190, 57], + [249, 188, 57], + [250, 186, 57], + [251, 184, 56], + [251, 182, 55], + [252, 179, 54], + [252, 177, 54], + [253, 174, 53], + [253, 172, 52], + [254, 169, 51], + [254, 167, 50], + [254, 164, 49], + [254, 161, 48], + [254, 158, 47], + [254, 155, 45], + [254, 153, 44], + [254, 150, 43], + [254, 147, 42], + [254, 144, 41], + [253, 141, 39], + [253, 138, 38], + [252, 135, 37], + [252, 132, 35], + [251, 129, 34], + [251, 126, 33], + [250, 123, 31], + [249, 120, 30], + [249, 117, 29], + [248, 114, 28], + [247, 111, 26], + [246, 108, 25], + [245, 105, 24], + [244, 102, 23], + [243, 99, 21], + [242, 96, 20], + [241, 93, 19], + [240, 91, 18], + [239, 88, 17], + [237, 85, 16], + [236, 83, 15], + [235, 80, 14], + [234, 78, 13], + [232, 75, 12], + [231, 73, 12], + [229, 71, 11], + [228, 69, 10], + [226, 67, 10], + [225, 65, 9], + [223, 63, 8], + [221, 61, 8], + [220, 59, 7], + [218, 57, 7], + [216, 55, 6], + [214, 53, 6], + [212, 51, 5], + [210, 49, 5], + [208, 47, 5], + [206, 45, 4], + [204, 43, 4], + [202, 42, 4], + [200, 40, 3], + [197, 38, 3], + [195, 37, 3], + [193, 35, 2], + [190, 33, 2], + [188, 32, 2], + [185, 30, 2], + [183, 29, 2], + [180, 27, 1], + [178, 26, 1], + [175, 24, 1], + [172, 23, 1], + [169, 22, 1], + [167, 20, 1], + [164, 19, 1], + [161, 18, 1], + [158, 16, 1], + [155, 15, 1], + [152, 14, 1], + [149, 13, 1], + [146, 11, 1], + [142, 10, 1], + [139, 9, 2], + [136, 8, 2], + [133, 7, 2], + [129, 6, 2], + [126, 5, 2], + [122, 4, 3], +]; +var fosphor = [ + [6, 0, 13], + [7, 0, 14], + [7, 0, 15], + [7, 0, 16], + [7, 0, 17], + [7, 0, 18], + [7, 0, 18], + [7, 0, 19], + [7, 0, 20], + [7, 0, 21], + [7, 0, 22], + [7, 0, 23], + [7, 0, 24], + [7, 0, 25], + [7, 0, 26], + [6, 0, 27], + [6, 0, 28], + [6, 0, 29], + [5, 0, 30], + [5, 0, 31], + [5, 0, 32], + [4, 0, 33], + [4, 0, 34], + [3, 0, 35], + [3, 0, 36], + [2, 0, 36], + [2, 0, 37], + [1, 0, 38], + [0, 0, 39], + [0, 0, 40], + [0, 1, 41], + [0, 2, 42], + [0, 3, 43], + [0, 4, 44], + [0, 5, 45], + [0, 5, 46], + [0, 6, 47], + [0, 7, 48], + [0, 8, 49], + [0, 9, 50], + [0, 10, 51], + [0, 12, 52], + [0, 13, 53], + [0, 14, 54], + [0, 15, 55], + [0, 16, 56], + [0, 18, 56], + [0, 19, 57], + [0, 20, 58], + [0, 22, 59], + [0, 23, 60], + [0, 24, 61], + [0, 26, 62], + [0, 27, 63], + [0, 29, 64], + [0, 31, 65], + [0, 32, 66], + [0, 34, 67], + [0, 36, 68], + [0, 37, 69], + [0, 39, 70], + [0, 41, 71], + [0, 43, 72], + [0, 44, 73], + [0, 46, 74], + [0, 48, 74], + [0, 50, 75], + [0, 52, 76], + [0, 54, 77], + [0, 56, 78], + [0, 58, 79], + [0, 60, 80], + [0, 63, 81], + [0, 65, 82], + [0, 67, 83], + [0, 69, 84], + [0, 71, 85], + [0, 74, 86], + [0, 76, 87], + [0, 79, 88], + [0, 81, 89], + [0, 83, 90], + [0, 86, 91], + [0, 88, 92], + [0, 91, 93], + [0, 94, 94], + [0, 94, 93], + [0, 95, 92], + [0, 96, 91], + [0, 97, 90], + [0, 98, 90], + [0, 99, 89], + [0, 100, 88], + [0, 101, 87], + [0, 102, 86], + [0, 103, 85], + [0, 104, 84], + [0, 105, 83], + [0, 106, 82], + [0, 107, 80], + [0, 108, 79], + [0, 109, 78], + [0, 110, 77], + [0, 111, 75], + [0, 112, 74], + [0, 112, 73], + [0, 113, 71], + [0, 114, 70], + [0, 115, 69], + [0, 116, 67], + [0, 117, 66], + [0, 118, 64], + [0, 119, 62], + [0, 120, 61], + [0, 121, 59], + [0, 122, 57], + [0, 123, 56], + [0, 124, 54], + [0, 125, 52], + [0, 126, 50], + [0, 127, 48], + [0, 128, 47], + [0, 129, 45], + [0, 130, 43], + [0, 131, 41], + [0, 132, 39], + [0, 132, 37], + [0, 133, 35], + [0, 134, 32], + [0, 135, 30], + [0, 136, 28], + [0, 137, 26], + [0, 138, 24], + [0, 139, 21], + [0, 140, 19], + [0, 141, 17], + [0, 142, 14], + [0, 143, 12], + [0, 144, 9], + [0, 145, 7], + [0, 146, 4], + [0, 147, 2], + [1, 148, 0], + [3, 149, 0], + [6, 150, 0], + [9, 150, 0], + [12, 151, 0], + [14, 152, 0], + [17, 153, 0], + [20, 154, 0], + [23, 155, 0], + [26, 156, 0], + [29, 157, 0], + [32, 158, 0], + [35, 159, 0], + [38, 160, 0], + [41, 161, 0], + [44, 162, 0], + [47, 163, 0], + [50, 164, 0], + [53, 165, 0], + [57, 166, 0], + [60, 167, 0], + [63, 168, 0], + [66, 169, 0], + [70, 170, 0], + [73, 170, 0], + [77, 171, 0], + [80, 172, 0], + [84, 173, 0], + [87, 174, 0], + [91, 175, 0], + [94, 176, 0], + [98, 177, 0], + [102, 178, 0], + [105, 179, 0], + [109, 180, 0], + [113, 181, 0], + [117, 182, 0], + [120, 183, 0], + [124, 184, 0], + [128, 185, 0], + [132, 186, 0], + [136, 187, 0], + [140, 188, 0], + [144, 188, 0], + [148, 189, 0], + [152, 190, 0], + [156, 191, 0], + [161, 192, 0], + [165, 193, 0], + [169, 194, 0], + [173, 195, 0], + [178, 196, 0], + [182, 197, 0], + [186, 198, 0], + [191, 199, 0], + [195, 200, 0], + [200, 201, 0], + [202, 199, 0], + [203, 197, 0], + [204, 194, 0], + [205, 191, 0], + [206, 189, 0], + [207, 186, 0], + [208, 183, 0], + [208, 180, 0], + [209, 177, 0], + [210, 174, 0], + [211, 172, 0], + [212, 169, 0], + [213, 166, 0], + [214, 163, 0], + [215, 159, 0], + [216, 156, 0], + [217, 153, 0], + [218, 150, 0], + [219, 147, 0], + [220, 144, 0], + [221, 140, 0], + [222, 137, 0], + [223, 134, 0], + [224, 130, 0], + [225, 127, 0], + [226, 123, 0], + [226, 120, 0], + [227, 116, 0], + [228, 113, 0], + [229, 109, 0], + [230, 106, 0], + [231, 102, 0], + [232, 98, 0], + [233, 95, 0], + [234, 91, 0], + [235, 87, 0], + [236, 83, 0], + [237, 79, 0], + [238, 76, 0], + [239, 72, 0], + [240, 68, 0], + [241, 64, 0], + [242, 60, 0], + [243, 56, 0], + [244, 52, 0], + [245, 47, 0], + [246, 43, 0], + [246, 39, 0], + [247, 35, 0], + [248, 31, 0], + [249, 26, 0], + [250, 22, 0], + [251, 18, 0], + [252, 13, 0], + [253, 9, 0], + [254, 4, 0], + [255, 0, 0], +]; +var viridis = [ + [68, 1, 84], + [68, 2, 86], + [69, 4, 87], + [69, 5, 89], + [70, 7, 90], + [70, 8, 92], + [70, 10, 93], + [70, 11, 94], + [71, 13, 96], + [71, 14, 97], + [71, 16, 99], + [71, 17, 100], + [71, 19, 101], + [72, 20, 103], + [72, 22, 104], + [72, 23, 105], + [72, 24, 106], + [72, 26, 108], + [72, 27, 109], + [72, 28, 110], + [72, 29, 111], + [72, 31, 112], + [72, 32, 113], + [72, 33, 115], + [72, 35, 116], + [72, 36, 117], + [72, 37, 118], + [72, 38, 119], + [72, 40, 120], + [72, 41, 121], + [71, 42, 122], + [71, 44, 122], + [71, 45, 123], + [71, 46, 124], + [71, 47, 125], + [70, 48, 126], + [70, 50, 126], + [70, 51, 127], + [70, 52, 128], + [69, 53, 129], + [69, 55, 129], + [69, 56, 130], + [68, 57, 131], + [68, 58, 131], + [68, 59, 132], + [67, 61, 132], + [67, 62, 133], + [66, 63, 133], + [66, 64, 134], + [66, 65, 134], + [65, 66, 135], + [65, 68, 135], + [64, 69, 136], + [64, 70, 136], + [63, 71, 136], + [63, 72, 137], + [62, 73, 137], + [62, 74, 137], + [62, 76, 138], + [61, 77, 138], + [61, 78, 138], + [60, 79, 138], + [60, 80, 139], + [59, 81, 139], + [59, 82, 139], + [58, 83, 139], + [58, 84, 140], + [57, 85, 140], + [57, 86, 140], + [56, 88, 140], + [56, 89, 140], + [55, 90, 140], + [55, 91, 141], + [54, 92, 141], + [54, 93, 141], + [53, 94, 141], + [53, 95, 141], + [52, 96, 141], + [52, 97, 141], + [51, 98, 141], + [51, 99, 141], + [50, 100, 142], + [50, 101, 142], + [49, 102, 142], + [49, 103, 142], + [49, 104, 142], + [48, 105, 142], + [48, 106, 142], + [47, 107, 142], + [47, 108, 142], + [46, 109, 142], + [46, 110, 142], + [46, 111, 142], + [45, 112, 142], + [45, 113, 142], + [44, 113, 142], + [44, 114, 142], + [44, 115, 142], + [43, 116, 142], + [43, 117, 142], + [42, 118, 142], + [42, 119, 142], + [42, 120, 142], + [41, 121, 142], + [41, 122, 142], + [41, 123, 142], + [40, 124, 142], + [40, 125, 142], + [39, 126, 142], + [39, 127, 142], + [39, 128, 142], + [38, 129, 142], + [38, 130, 142], + [38, 130, 142], + [37, 131, 142], + [37, 132, 142], + [37, 133, 142], + [36, 134, 142], + [36, 135, 142], + [35, 136, 142], + [35, 137, 142], + [35, 138, 141], + [34, 139, 141], + [34, 140, 141], + [34, 141, 141], + [33, 142, 141], + [33, 143, 141], + [33, 144, 141], + [33, 145, 140], + [32, 146, 140], + [32, 146, 140], + [32, 147, 140], + [31, 148, 140], + [31, 149, 139], + [31, 150, 139], + [31, 151, 139], + [31, 152, 139], + [31, 153, 138], + [31, 154, 138], + [30, 155, 138], + [30, 156, 137], + [30, 157, 137], + [31, 158, 137], + [31, 159, 136], + [31, 160, 136], + [31, 161, 136], + [31, 161, 135], + [31, 162, 135], + [32, 163, 134], + [32, 164, 134], + [33, 165, 133], + [33, 166, 133], + [34, 167, 133], + [34, 168, 132], + [35, 169, 131], + [36, 170, 131], + [37, 171, 130], + [37, 172, 130], + [38, 173, 129], + [39, 173, 129], + [40, 174, 128], + [41, 175, 127], + [42, 176, 127], + [44, 177, 126], + [45, 178, 125], + [46, 179, 124], + [47, 180, 124], + [49, 181, 123], + [50, 182, 122], + [52, 182, 121], + [53, 183, 121], + [55, 184, 120], + [56, 185, 119], + [58, 186, 118], + [59, 187, 117], + [61, 188, 116], + [63, 188, 115], + [64, 189, 114], + [66, 190, 113], + [68, 191, 112], + [70, 192, 111], + [72, 193, 110], + [74, 193, 109], + [76, 194, 108], + [78, 195, 107], + [80, 196, 106], + [82, 197, 105], + [84, 197, 104], + [86, 198, 103], + [88, 199, 101], + [90, 200, 100], + [92, 200, 99], + [94, 201, 98], + [96, 202, 96], + [99, 203, 95], + [101, 203, 94], + [103, 204, 92], + [105, 205, 91], + [108, 205, 90], + [110, 206, 88], + [112, 207, 87], + [115, 208, 86], + [117, 208, 84], + [119, 209, 83], + [122, 209, 81], + [124, 210, 80], + [127, 211, 78], + [129, 211, 77], + [132, 212, 75], + [134, 213, 73], + [137, 213, 72], + [139, 214, 70], + [142, 214, 69], + [144, 215, 67], + [147, 215, 65], + [149, 216, 64], + [152, 216, 62], + [155, 217, 60], + [157, 217, 59], + [160, 218, 57], + [162, 218, 55], + [165, 219, 54], + [168, 219, 52], + [170, 220, 50], + [173, 220, 48], + [176, 221, 47], + [178, 221, 45], + [181, 222, 43], + [184, 222, 41], + [186, 222, 40], + [189, 223, 38], + [192, 223, 37], + [194, 223, 35], + [197, 224, 33], + [200, 224, 32], + [202, 225, 31], + [205, 225, 29], + [208, 225, 28], + [210, 226, 27], + [213, 226, 26], + [216, 226, 25], + [218, 227, 25], + [221, 227, 24], + [223, 227, 24], + [226, 228, 24], + [229, 228, 25], + [231, 228, 25], + [234, 229, 26], + [236, 229, 27], + [239, 229, 28], + [241, 229, 29], + [244, 230, 30], + [246, 230, 32], + [248, 230, 33], + [251, 231, 35], + [253, 231, 37], +]; +var inferno = [ + [0, 0, 4], + [1, 0, 5], + [1, 1, 6], + [1, 1, 8], + [2, 1, 10], + [2, 2, 12], + [2, 2, 14], + [3, 2, 16], + [4, 3, 18], + [4, 3, 20], + [5, 4, 23], + [6, 4, 25], + [7, 5, 27], + [8, 5, 29], + [9, 6, 31], + [10, 7, 34], + [11, 7, 36], + [12, 8, 38], + [13, 8, 41], + [14, 9, 43], + [16, 9, 45], + [17, 10, 48], + [18, 10, 50], + [20, 11, 52], + [21, 11, 55], + [22, 11, 57], + [24, 12, 60], + [25, 12, 62], + [27, 12, 65], + [28, 12, 67], + [30, 12, 69], + [31, 12, 72], + [33, 12, 74], + [35, 12, 76], + [36, 12, 79], + [38, 12, 81], + [40, 11, 83], + [41, 11, 85], + [43, 11, 87], + [45, 11, 89], + [47, 10, 91], + [49, 10, 92], + [50, 10, 94], + [52, 10, 95], + [54, 9, 97], + [56, 9, 98], + [57, 9, 99], + [59, 9, 100], + [61, 9, 101], + [62, 9, 102], + [64, 10, 103], + [66, 10, 104], + [68, 10, 104], + [69, 10, 105], + [71, 11, 106], + [73, 11, 106], + [74, 12, 107], + [76, 12, 107], + [77, 13, 108], + [79, 13, 108], + [81, 14, 108], + [82, 14, 109], + [84, 15, 109], + [85, 15, 109], + [87, 16, 110], + [89, 16, 110], + [90, 17, 110], + [92, 18, 110], + [93, 18, 110], + [95, 19, 110], + [97, 19, 110], + [98, 20, 110], + [100, 21, 110], + [101, 21, 110], + [103, 22, 110], + [105, 22, 110], + [106, 23, 110], + [108, 24, 110], + [109, 24, 110], + [111, 25, 110], + [113, 25, 110], + [114, 26, 110], + [116, 26, 110], + [117, 27, 110], + [119, 28, 109], + [120, 28, 109], + [122, 29, 109], + [124, 29, 109], + [125, 30, 109], + [127, 30, 108], + [128, 31, 108], + [130, 32, 108], + [132, 32, 107], + [133, 33, 107], + [135, 33, 107], + [136, 34, 106], + [138, 34, 106], + [140, 35, 105], + [141, 35, 105], + [143, 36, 105], + [144, 37, 104], + [146, 37, 104], + [147, 38, 103], + [149, 38, 103], + [151, 39, 102], + [152, 39, 102], + [154, 40, 101], + [155, 41, 100], + [157, 41, 100], + [159, 42, 99], + [160, 42, 99], + [162, 43, 98], + [163, 44, 97], + [165, 44, 96], + [166, 45, 96], + [168, 46, 95], + [169, 46, 94], + [171, 47, 94], + [173, 48, 93], + [174, 48, 92], + [176, 49, 91], + [177, 50, 90], + [179, 50, 90], + [180, 51, 89], + [182, 52, 88], + [183, 53, 87], + [185, 53, 86], + [186, 54, 85], + [188, 55, 84], + [189, 56, 83], + [191, 57, 82], + [192, 58, 81], + [193, 58, 80], + [195, 59, 79], + [196, 60, 78], + [198, 61, 77], + [199, 62, 76], + [200, 63, 75], + [202, 64, 74], + [203, 65, 73], + [204, 66, 72], + [206, 67, 71], + [207, 68, 70], + [208, 69, 69], + [210, 70, 68], + [211, 71, 67], + [212, 72, 66], + [213, 74, 65], + [215, 75, 63], + [216, 76, 62], + [217, 77, 61], + [218, 78, 60], + [219, 80, 59], + [221, 81, 58], + [222, 82, 56], + [223, 83, 55], + [224, 85, 54], + [225, 86, 53], + [226, 87, 52], + [227, 89, 51], + [228, 90, 49], + [229, 92, 48], + [230, 93, 47], + [231, 94, 46], + [232, 96, 45], + [233, 97, 43], + [234, 99, 42], + [235, 100, 41], + [235, 102, 40], + [236, 103, 38], + [237, 105, 37], + [238, 106, 36], + [239, 108, 35], + [239, 110, 33], + [240, 111, 32], + [241, 113, 31], + [241, 115, 29], + [242, 116, 28], + [243, 118, 27], + [243, 120, 25], + [244, 121, 24], + [245, 123, 23], + [245, 125, 21], + [246, 126, 20], + [246, 128, 19], + [247, 130, 18], + [247, 132, 16], + [248, 133, 15], + [248, 135, 14], + [248, 137, 12], + [249, 139, 11], + [249, 140, 10], + [249, 142, 9], + [250, 144, 8], + [250, 146, 7], + [250, 148, 7], + [251, 150, 6], + [251, 151, 6], + [251, 153, 6], + [251, 155, 6], + [251, 157, 7], + [252, 159, 7], + [252, 161, 8], + [252, 163, 9], + [252, 165, 10], + [252, 166, 12], + [252, 168, 13], + [252, 170, 15], + [252, 172, 17], + [252, 174, 18], + [252, 176, 20], + [252, 178, 22], + [252, 180, 24], + [251, 182, 26], + [251, 184, 29], + [251, 186, 31], + [251, 188, 33], + [251, 190, 35], + [250, 192, 38], + [250, 194, 40], + [250, 196, 42], + [250, 198, 45], + [249, 199, 47], + [249, 201, 50], + [249, 203, 53], + [248, 205, 55], + [248, 207, 58], + [247, 209, 61], + [247, 211, 64], + [246, 213, 67], + [246, 215, 70], + [245, 217, 73], + [245, 219, 76], + [244, 221, 79], + [244, 223, 83], + [244, 225, 86], + [243, 227, 90], + [243, 229, 93], + [242, 230, 97], + [242, 232, 101], + [242, 234, 105], + [241, 236, 109], + [241, 237, 113], + [241, 239, 117], + [241, 241, 121], + [242, 242, 125], + [242, 244, 130], + [243, 245, 134], + [243, 246, 138], + [244, 248, 142], + [245, 249, 146], + [246, 250, 150], + [248, 251, 154], + [249, 252, 157], + [250, 253, 161], + [252, 255, 164], +]; +var magma = [ + [0, 0, 4], + [1, 0, 5], + [1, 1, 6], + [1, 1, 8], + [2, 1, 9], + [2, 2, 11], + [2, 2, 13], + [3, 3, 15], + [3, 3, 18], + [4, 4, 20], + [5, 4, 22], + [6, 5, 24], + [6, 5, 26], + [7, 6, 28], + [8, 7, 30], + [9, 7, 32], + [10, 8, 34], + [11, 9, 36], + [12, 9, 38], + [13, 10, 41], + [14, 11, 43], + [16, 11, 45], + [17, 12, 47], + [18, 13, 49], + [19, 13, 52], + [20, 14, 54], + [21, 14, 56], + [22, 15, 59], + [24, 15, 61], + [25, 16, 63], + [26, 16, 66], + [28, 16, 68], + [29, 17, 71], + [30, 17, 73], + [32, 17, 75], + [33, 17, 78], + [34, 17, 80], + [36, 18, 83], + [37, 18, 85], + [39, 18, 88], + [41, 17, 90], + [42, 17, 92], + [44, 17, 95], + [45, 17, 97], + [47, 17, 99], + [49, 17, 101], + [51, 16, 103], + [52, 16, 105], + [54, 16, 107], + [56, 16, 108], + [57, 15, 110], + [59, 15, 112], + [61, 15, 113], + [63, 15, 114], + [64, 15, 116], + [66, 15, 117], + [68, 15, 118], + [69, 16, 119], + [71, 16, 120], + [73, 16, 120], + [74, 16, 121], + [76, 17, 122], + [78, 17, 123], + [79, 18, 123], + [81, 18, 124], + [82, 19, 124], + [84, 19, 125], + [86, 20, 125], + [87, 21, 126], + [89, 21, 126], + [90, 22, 126], + [92, 22, 127], + [93, 23, 127], + [95, 24, 127], + [96, 24, 128], + [98, 25, 128], + [100, 26, 128], + [101, 26, 128], + [103, 27, 128], + [104, 28, 129], + [106, 28, 129], + [107, 29, 129], + [109, 29, 129], + [110, 30, 129], + [112, 31, 129], + [114, 31, 129], + [115, 32, 129], + [117, 33, 129], + [118, 33, 129], + [120, 34, 129], + [121, 34, 130], + [123, 35, 130], + [124, 35, 130], + [126, 36, 130], + [128, 37, 130], + [129, 37, 129], + [131, 38, 129], + [132, 38, 129], + [134, 39, 129], + [136, 39, 129], + [137, 40, 129], + [139, 41, 129], + [140, 41, 129], + [142, 42, 129], + [144, 42, 129], + [145, 43, 129], + [147, 43, 128], + [148, 44, 128], + [150, 44, 128], + [152, 45, 128], + [153, 45, 128], + [155, 46, 127], + [156, 46, 127], + [158, 47, 127], + [160, 47, 127], + [161, 48, 126], + [163, 48, 126], + [165, 49, 126], + [166, 49, 125], + [168, 50, 125], + [170, 51, 125], + [171, 51, 124], + [173, 52, 124], + [174, 52, 123], + [176, 53, 123], + [178, 53, 123], + [179, 54, 122], + [181, 54, 122], + [183, 55, 121], + [184, 55, 121], + [186, 56, 120], + [188, 57, 120], + [189, 57, 119], + [191, 58, 119], + [192, 58, 118], + [194, 59, 117], + [196, 60, 117], + [197, 60, 116], + [199, 61, 115], + [200, 62, 115], + [202, 62, 114], + [204, 63, 113], + [205, 64, 113], + [207, 64, 112], + [208, 65, 111], + [210, 66, 111], + [211, 67, 110], + [213, 68, 109], + [214, 69, 108], + [216, 69, 108], + [217, 70, 107], + [219, 71, 106], + [220, 72, 105], + [222, 73, 104], + [223, 74, 104], + [224, 76, 103], + [226, 77, 102], + [227, 78, 101], + [228, 79, 100], + [229, 80, 100], + [231, 82, 99], + [232, 83, 98], + [233, 84, 98], + [234, 86, 97], + [235, 87, 96], + [236, 88, 96], + [237, 90, 95], + [238, 91, 94], + [239, 93, 94], + [240, 95, 94], + [241, 96, 93], + [242, 98, 93], + [242, 100, 92], + [243, 101, 92], + [244, 103, 92], + [244, 105, 92], + [245, 107, 92], + [246, 108, 92], + [246, 110, 92], + [247, 112, 92], + [247, 114, 92], + [248, 116, 92], + [248, 118, 92], + [249, 120, 93], + [249, 121, 93], + [249, 123, 93], + [250, 125, 94], + [250, 127, 94], + [250, 129, 95], + [251, 131, 95], + [251, 133, 96], + [251, 135, 97], + [252, 137, 97], + [252, 138, 98], + [252, 140, 99], + [252, 142, 100], + [252, 144, 101], + [253, 146, 102], + [253, 148, 103], + [253, 150, 104], + [253, 152, 105], + [253, 154, 106], + [253, 155, 107], + [254, 157, 108], + [254, 159, 109], + [254, 161, 110], + [254, 163, 111], + [254, 165, 113], + [254, 167, 114], + [254, 169, 115], + [254, 170, 116], + [254, 172, 118], + [254, 174, 119], + [254, 176, 120], + [254, 178, 122], + [254, 180, 123], + [254, 182, 124], + [254, 183, 126], + [254, 185, 127], + [254, 187, 129], + [254, 189, 130], + [254, 191, 132], + [254, 193, 133], + [254, 194, 135], + [254, 196, 136], + [254, 198, 138], + [254, 200, 140], + [254, 202, 141], + [254, 204, 143], + [254, 205, 144], + [254, 207, 146], + [254, 209, 148], + [254, 211, 149], + [254, 213, 151], + [254, 215, 153], + [254, 216, 154], + [253, 218, 156], + [253, 220, 158], + [253, 222, 160], + [253, 224, 161], + [253, 226, 163], + [253, 227, 165], + [253, 229, 167], + [253, 231, 169], + [253, 233, 170], + [253, 235, 172], + [252, 236, 174], + [252, 238, 176], + [252, 240, 178], + [252, 242, 180], + [252, 244, 182], + [252, 246, 184], + [252, 247, 185], + [252, 249, 187], + [252, 251, 189], + [252, 253, 191], +]; +var jet = [ + [0, 0, 128], + [0, 0, 132], + [0, 0, 137], + [0, 0, 141], + [0, 0, 146], + [0, 0, 150], + [0, 0, 155], + [0, 0, 159], + [0, 0, 164], + [0, 0, 168], + [0, 0, 173], + [0, 0, 178], + [0, 0, 182], + [0, 0, 187], + [0, 0, 191], + [0, 0, 196], + [0, 0, 200], + [0, 0, 205], + [0, 0, 209], + [0, 0, 214], + [0, 0, 218], + [0, 0, 223], + [0, 0, 227], + [0, 0, 232], + [0, 0, 237], + [0, 0, 241], + [0, 0, 246], + [0, 0, 250], + [0, 0, 255], + [0, 0, 255], + [0, 0, 255], + [0, 0, 255], + [0, 0, 255], + [0, 4, 255], + [0, 8, 255], + [0, 12, 255], + [0, 16, 255], + [0, 20, 255], + [0, 24, 255], + [0, 28, 255], + [0, 32, 255], + [0, 36, 255], + [0, 40, 255], + [0, 44, 255], + [0, 48, 255], + [0, 52, 255], + [0, 56, 255], + [0, 60, 255], + [0, 64, 255], + [0, 68, 255], + [0, 72, 255], + [0, 76, 255], + [0, 80, 255], + [0, 84, 255], + [0, 88, 255], + [0, 92, 255], + [0, 96, 255], + [0, 100, 255], + [0, 104, 255], + [0, 108, 255], + [0, 112, 255], + [0, 116, 255], + [0, 120, 255], + [0, 124, 255], + [0, 128, 255], + [0, 132, 255], + [0, 136, 255], + [0, 140, 255], + [0, 144, 255], + [0, 148, 255], + [0, 152, 255], + [0, 156, 255], + [0, 160, 255], + [0, 164, 255], + [0, 168, 255], + [0, 172, 255], + [0, 176, 255], + [0, 180, 255], + [0, 184, 255], + [0, 188, 255], + [0, 192, 255], + [0, 196, 255], + [0, 200, 255], + [0, 204, 255], + [0, 208, 255], + [0, 212, 255], + [0, 216, 255], + [0, 220, 254], + [0, 224, 251], + [0, 228, 248], + [2, 232, 244], + [6, 236, 241], + [9, 240, 238], + [12, 244, 235], + [15, 248, 231], + [19, 252, 228], + [22, 255, 225], + [25, 255, 222], + [28, 255, 219], + [31, 255, 215], + [35, 255, 212], + [38, 255, 209], + [41, 255, 206], + [44, 255, 202], + [48, 255, 199], + [51, 255, 196], + [54, 255, 193], + [57, 255, 190], + [60, 255, 186], + [64, 255, 183], + [67, 255, 180], + [70, 255, 177], + [73, 255, 173], + [77, 255, 170], + [80, 255, 167], + [83, 255, 164], + [86, 255, 160], + [90, 255, 157], + [93, 255, 154], + [96, 255, 151], + [99, 255, 148], + [102, 255, 144], + [106, 255, 141], + [109, 255, 138], + [112, 255, 135], + [115, 255, 131], + [119, 255, 128], + [122, 255, 125], + [125, 255, 122], + [128, 255, 119], + [131, 255, 115], + [135, 255, 112], + [138, 255, 109], + [141, 255, 106], + [144, 255, 102], + [148, 255, 99], + [151, 255, 96], + [154, 255, 93], + [157, 255, 90], + [160, 255, 86], + [164, 255, 83], + [167, 255, 80], + [170, 255, 77], + [173, 255, 73], + [177, 255, 70], + [180, 255, 67], + [183, 255, 64], + [186, 255, 60], + [190, 255, 57], + [193, 255, 54], + [196, 255, 51], + [199, 255, 48], + [202, 255, 44], + [206, 255, 41], + [209, 255, 38], + [212, 255, 35], + [215, 255, 31], + [219, 255, 28], + [222, 255, 25], + [225, 255, 22], + [228, 255, 19], + [231, 255, 15], + [235, 255, 12], + [238, 255, 9], + [241, 252, 6], + [244, 248, 2], + [248, 245, 0], + [251, 241, 0], + [254, 237, 0], + [255, 234, 0], + [255, 230, 0], + [255, 226, 0], + [255, 222, 0], + [255, 219, 0], + [255, 215, 0], + [255, 211, 0], + [255, 208, 0], + [255, 204, 0], + [255, 200, 0], + [255, 196, 0], + [255, 193, 0], + [255, 189, 0], + [255, 185, 0], + [255, 182, 0], + [255, 178, 0], + [255, 174, 0], + [255, 171, 0], + [255, 167, 0], + [255, 163, 0], + [255, 159, 0], + [255, 156, 0], + [255, 152, 0], + [255, 148, 0], + [255, 145, 0], + [255, 141, 0], + [255, 137, 0], + [255, 134, 0], + [255, 130, 0], + [255, 126, 0], + [255, 122, 0], + [255, 119, 0], + [255, 115, 0], + [255, 111, 0], + [255, 108, 0], + [255, 104, 0], + [255, 100, 0], + [255, 96, 0], + [255, 93, 0], + [255, 89, 0], + [255, 85, 0], + [255, 82, 0], + [255, 78, 0], + [255, 74, 0], + [255, 71, 0], + [255, 67, 0], + [255, 63, 0], + [255, 59, 0], + [255, 56, 0], + [255, 52, 0], + [255, 48, 0], + [255, 45, 0], + [255, 41, 0], + [255, 37, 0], + [255, 34, 0], + [255, 30, 0], + [255, 26, 0], + [255, 22, 0], + [255, 19, 0], + [250, 15, 0], + [246, 11, 0], + [241, 8, 0], + [237, 4, 0], + [232, 0, 0], + [228, 0, 0], + [223, 0, 0], + [218, 0, 0], + [214, 0, 0], + [209, 0, 0], + [205, 0, 0], + [200, 0, 0], + [196, 0, 0], + [191, 0, 0], + [187, 0, 0], + [182, 0, 0], + [178, 0, 0], + [173, 0, 0], + [168, 0, 0], + [164, 0, 0], + [159, 0, 0], + [155, 0, 0], + [150, 0, 0], + [146, 0, 0], + [141, 0, 0], + [137, 0, 0], + [132, 0, 0], + [128, 0, 0], +]; +var binary = [ + [255, 255, 255], + [254, 254, 254], + [253, 253, 253], + [252, 252, 252], + [251, 251, 251], + [250, 250, 250], + [249, 249, 249], + [248, 248, 248], + [247, 247, 247], + [246, 246, 246], + [245, 245, 245], + [244, 244, 244], + [243, 243, 243], + [242, 242, 242], + [241, 241, 241], + [240, 240, 240], + [239, 239, 239], + [238, 238, 238], + [237, 237, 237], + [236, 236, 236], + [235, 235, 235], + [234, 234, 234], + [233, 233, 233], + [232, 232, 232], + [231, 231, 231], + [230, 230, 230], + [229, 229, 229], + [228, 228, 228], + [227, 227, 227], + [226, 226, 226], + [225, 225, 225], + [224, 224, 224], + [223, 223, 223], + [222, 222, 222], + [221, 221, 221], + [220, 220, 220], + [219, 219, 219], + [218, 218, 218], + [217, 217, 217], + [216, 216, 216], + [215, 215, 215], + [214, 214, 214], + [213, 213, 213], + [212, 212, 212], + [211, 211, 211], + [210, 210, 210], + [209, 209, 209], + [208, 208, 208], + [207, 207, 207], + [206, 206, 206], + [205, 205, 205], + [204, 204, 204], + [203, 203, 203], + [202, 202, 202], + [201, 201, 201], + [200, 200, 200], + [199, 199, 199], + [198, 198, 198], + [197, 197, 197], + [196, 196, 196], + [195, 195, 195], + [194, 194, 194], + [193, 193, 193], + [192, 192, 192], + [191, 191, 191], + [190, 190, 190], + [189, 189, 189], + [188, 188, 188], + [187, 187, 187], + [186, 186, 186], + [185, 185, 185], + [184, 184, 184], + [183, 183, 183], + [182, 182, 182], + [181, 181, 181], + [180, 180, 180], + [179, 179, 179], + [178, 178, 178], + [177, 177, 177], + [176, 176, 176], + [175, 175, 175], + [174, 174, 174], + [173, 173, 173], + [172, 172, 172], + [171, 171, 171], + [170, 170, 170], + [169, 169, 169], + [168, 168, 168], + [167, 167, 167], + [166, 166, 166], + [165, 165, 165], + [164, 164, 164], + [163, 163, 163], + [162, 162, 162], + [161, 161, 161], + [160, 160, 160], + [159, 159, 159], + [158, 158, 158], + [157, 157, 157], + [156, 156, 156], + [155, 155, 155], + [154, 154, 154], + [153, 153, 153], + [152, 152, 152], + [151, 151, 151], + [150, 150, 150], + [149, 149, 149], + [148, 148, 148], + [147, 147, 147], + [146, 146, 146], + [145, 145, 145], + [144, 144, 144], + [143, 143, 143], + [142, 142, 142], + [141, 141, 141], + [140, 140, 140], + [139, 139, 139], + [138, 138, 138], + [137, 137, 137], + [136, 136, 136], + [135, 135, 135], + [134, 134, 134], + [133, 133, 133], + [132, 132, 132], + [131, 131, 131], + [130, 130, 130], + [129, 129, 129], + [128, 128, 128], + [127, 127, 127], + [126, 126, 126], + [125, 125, 125], + [124, 124, 124], + [123, 123, 123], + [122, 122, 122], + [121, 121, 121], + [120, 120, 120], + [119, 119, 119], + [118, 118, 118], + [117, 117, 117], + [116, 116, 116], + [115, 115, 115], + [114, 114, 114], + [113, 113, 113], + [112, 112, 112], + [111, 111, 111], + [110, 110, 110], + [109, 109, 109], + [108, 108, 108], + [107, 107, 107], + [106, 106, 106], + [105, 105, 105], + [104, 104, 104], + [103, 103, 103], + [102, 102, 102], + [101, 101, 101], + [100, 100, 100], + [99, 99, 99], + [98, 98, 98], + [97, 97, 97], + [96, 96, 96], + [95, 95, 95], + [94, 94, 94], + [93, 93, 93], + [92, 92, 92], + [91, 91, 91], + [90, 90, 90], + [89, 89, 89], + [88, 88, 88], + [87, 87, 87], + [86, 86, 86], + [85, 85, 85], + [84, 84, 84], + [83, 83, 83], + [82, 82, 82], + [81, 81, 81], + [80, 80, 80], + [79, 79, 79], + [78, 78, 78], + [77, 77, 77], + [76, 76, 76], + [75, 75, 75], + [74, 74, 74], + [73, 73, 73], + [72, 72, 72], + [71, 71, 71], + [70, 70, 70], + [69, 69, 69], + [68, 68, 68], + [67, 67, 67], + [66, 66, 66], + [65, 65, 65], + [64, 64, 64], + [63, 63, 63], + [62, 62, 62], + [61, 61, 61], + [60, 60, 60], + [59, 59, 59], + [58, 58, 58], + [57, 57, 57], + [56, 56, 56], + [55, 55, 55], + [54, 54, 54], + [53, 53, 53], + [52, 52, 52], + [51, 51, 51], + [50, 50, 50], + [49, 49, 49], + [48, 48, 48], + [47, 47, 47], + [46, 46, 46], + [45, 45, 45], + [44, 44, 44], + [43, 43, 43], + [42, 42, 42], + [41, 41, 41], + [40, 40, 40], + [39, 39, 39], + [38, 38, 38], + [37, 37, 37], + [36, 36, 36], + [35, 35, 35], + [34, 34, 34], + [33, 33, 33], + [32, 32, 32], + [31, 31, 31], + [30, 30, 30], + [29, 29, 29], + [28, 28, 28], + [27, 27, 27], + [26, 26, 26], + [25, 25, 25], + [24, 24, 24], + [23, 23, 23], + [22, 22, 22], + [21, 21, 21], + [20, 20, 20], + [19, 19, 19], + [18, 18, 18], + [17, 17, 17], + [16, 16, 16], + [15, 15, 15], + [14, 14, 14], + [13, 13, 13], + [12, 12, 12], + [11, 11, 11], + [10, 10, 10], + [9, 9, 9], + [8, 8, 8], + [7, 7, 7], + [6, 6, 6], + [5, 5, 5], + [4, 4, 4], + [3, 3, 3], + [2, 2, 2], + [1, 1, 1], + [0, 0, 0], +]; +var colormaps = [turbo, fosphor, viridis, inferno, magma, jet, binary]; diff --git a/gui/src/waterfall/waterfall.css b/gui/src/assets/waterfall/waterfall.css similarity index 100% rename from gui/src/waterfall/waterfall.css rename to gui/src/assets/waterfall/waterfall.css diff --git a/gui/src/chat-module.html b/gui/src/chat-module.html deleted file mode 100644 index 7f161b2b..00000000 --- a/gui/src/chat-module.html +++ /dev/null @@ -1,843 +0,0 @@ - - - - - - - - - - - - - FreeDATA - CHAT - - - - - - - - - - - - -
-
-
- -
-
- - - - - -
-
-
-
-
-
-
-
- -
-
- - - - - - - - - - - - - ------ -
-
- -
- -
- - -
- -
- - - -
- - -
- -
- - - -
- - - -
-
-
-
-
-
- - - - - - - - - - - - - - diff --git a/gui/src/components/chat.vue b/gui/src/components/chat.vue new file mode 100644 index 00000000..d625d309 --- /dev/null +++ b/gui/src/components/chat.vue @@ -0,0 +1,523 @@ + + + diff --git a/gui/src/components/chat_conversations.vue b/gui/src/components/chat_conversations.vue new file mode 100644 index 00000000..d49d4b8c --- /dev/null +++ b/gui/src/components/chat_conversations.vue @@ -0,0 +1,84 @@ + + diff --git a/gui/src/components/chat_messages.vue b/gui/src/components/chat_messages.vue new file mode 100644 index 00000000..bb00ca6e --- /dev/null +++ b/gui/src/components/chat_messages.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/gui/src/components/chat_messages_action_menu.vue b/gui/src/components/chat_messages_action_menu.vue new file mode 100644 index 00000000..6f14d283 --- /dev/null +++ b/gui/src/components/chat_messages_action_menu.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/gui/src/components/chat_messages_broadcast_received.vue b/gui/src/components/chat_messages_broadcast_received.vue new file mode 100644 index 00000000..7a67d96a --- /dev/null +++ b/gui/src/components/chat_messages_broadcast_received.vue @@ -0,0 +1,96 @@ + + + diff --git a/gui/src/components/chat_messages_broadcast_sent.vue b/gui/src/components/chat_messages_broadcast_sent.vue new file mode 100644 index 00000000..a1ee0ad0 --- /dev/null +++ b/gui/src/components/chat_messages_broadcast_sent.vue @@ -0,0 +1,113 @@ + + + diff --git a/gui/src/components/chat_messages_received.vue b/gui/src/components/chat_messages_received.vue new file mode 100644 index 00000000..aaa11afb --- /dev/null +++ b/gui/src/components/chat_messages_received.vue @@ -0,0 +1,135 @@ + + + diff --git a/gui/src/components/chat_messages_sent.vue b/gui/src/components/chat_messages_sent.vue new file mode 100644 index 00000000..bc21f4ad --- /dev/null +++ b/gui/src/components/chat_messages_sent.vue @@ -0,0 +1,171 @@ + + + diff --git a/gui/src/components/chat_navbar.vue b/gui/src/components/chat_navbar.vue new file mode 100644 index 00000000..46a2bc72 --- /dev/null +++ b/gui/src/components/chat_navbar.vue @@ -0,0 +1,195 @@ + + + diff --git a/gui/src/components/chat_new_message.vue b/gui/src/components/chat_new_message.vue new file mode 100644 index 00000000..fe997c63 --- /dev/null +++ b/gui/src/components/chat_new_message.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/gui/src/components/main.vue b/gui/src/components/main.vue new file mode 100644 index 00000000..2997f922 --- /dev/null +++ b/gui/src/components/main.vue @@ -0,0 +1,570 @@ + + + diff --git a/gui/src/components/main_active_audio_level.vue b/gui/src/components/main_active_audio_level.vue new file mode 100644 index 00000000..ff7d3cb0 --- /dev/null +++ b/gui/src/components/main_active_audio_level.vue @@ -0,0 +1,188 @@ + + diff --git a/gui/src/components/main_active_broadcasts.vue b/gui/src/components/main_active_broadcasts.vue new file mode 100644 index 00000000..33c0611c --- /dev/null +++ b/gui/src/components/main_active_broadcasts.vue @@ -0,0 +1,121 @@ + + diff --git a/gui/src/components/main_active_heard_stations.vue b/gui/src/components/main_active_heard_stations.vue new file mode 100644 index 00000000..0c42f66b --- /dev/null +++ b/gui/src/components/main_active_heard_stations.vue @@ -0,0 +1,105 @@ + + diff --git a/gui/src/components/main_active_rig_control.vue b/gui/src/components/main_active_rig_control.vue new file mode 100644 index 00000000..eaab7b66 --- /dev/null +++ b/gui/src/components/main_active_rig_control.vue @@ -0,0 +1,153 @@ + + + diff --git a/gui/src/components/main_active_stats.vue b/gui/src/components/main_active_stats.vue new file mode 100644 index 00000000..03ef9fe7 --- /dev/null +++ b/gui/src/components/main_active_stats.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/gui/src/components/main_audio.vue b/gui/src/components/main_audio.vue new file mode 100644 index 00000000..62bfc928 --- /dev/null +++ b/gui/src/components/main_audio.vue @@ -0,0 +1,61 @@ + + + diff --git a/gui/src/components/main_footer_navbar.vue b/gui/src/components/main_footer_navbar.vue new file mode 100644 index 00000000..0ecb3ed6 --- /dev/null +++ b/gui/src/components/main_footer_navbar.vue @@ -0,0 +1,251 @@ + + + +ww diff --git a/gui/src/components/main_modals.vue b/gui/src/components/main_modals.vue new file mode 100644 index 00000000..cd47990b --- /dev/null +++ b/gui/src/components/main_modals.vue @@ -0,0 +1,1214 @@ + + + diff --git a/gui/src/components/main_my_station.vue b/gui/src/components/main_my_station.vue new file mode 100644 index 00000000..0b27cfee --- /dev/null +++ b/gui/src/components/main_my_station.vue @@ -0,0 +1,124 @@ + + + diff --git a/gui/src/components/main_rig_control.vue b/gui/src/components/main_rig_control.vue new file mode 100644 index 00000000..d8307528 --- /dev/null +++ b/gui/src/components/main_rig_control.vue @@ -0,0 +1,276 @@ + + + diff --git a/gui/src/components/main_top_navbar.vue b/gui/src/components/main_top_navbar.vue new file mode 100644 index 00000000..52e826ac --- /dev/null +++ b/gui/src/components/main_top_navbar.vue @@ -0,0 +1,128 @@ + + + diff --git a/gui/src/components/main_updater.vue b/gui/src/components/main_updater.vue new file mode 100644 index 00000000..f289d697 --- /dev/null +++ b/gui/src/components/main_updater.vue @@ -0,0 +1,87 @@ + + + diff --git a/gui/src/components/settings.vue b/gui/src/components/settings.vue new file mode 100644 index 00000000..e9975578 --- /dev/null +++ b/gui/src/components/settings.vue @@ -0,0 +1,171 @@ + + diff --git a/gui/src/components/settings_chat.vue b/gui/src/components/settings_chat.vue new file mode 100644 index 00000000..e2ac997b --- /dev/null +++ b/gui/src/components/settings_chat.vue @@ -0,0 +1,128 @@ + + + diff --git a/gui/src/components/settings_exp.vue b/gui/src/components/settings_exp.vue new file mode 100644 index 00000000..d3e3d3ba --- /dev/null +++ b/gui/src/components/settings_exp.vue @@ -0,0 +1,99 @@ + + + diff --git a/gui/src/components/settings_gui.vue b/gui/src/components/settings_gui.vue new file mode 100644 index 00000000..19a15e60 --- /dev/null +++ b/gui/src/components/settings_gui.vue @@ -0,0 +1,158 @@ + + diff --git a/gui/src/components/settings_hamlib.vue b/gui/src/components/settings_hamlib.vue new file mode 100644 index 00000000..5ed01765 --- /dev/null +++ b/gui/src/components/settings_hamlib.vue @@ -0,0 +1,522 @@ + + + diff --git a/gui/src/components/settings_modem.vue b/gui/src/components/settings_modem.vue new file mode 100644 index 00000000..bb422550 --- /dev/null +++ b/gui/src/components/settings_modem.vue @@ -0,0 +1,216 @@ + + + diff --git a/gui/src/components/settings_web.vue b/gui/src/components/settings_web.vue new file mode 100644 index 00000000..aaa0aa43 --- /dev/null +++ b/gui/src/components/settings_web.vue @@ -0,0 +1,53 @@ + + + diff --git a/gui/src/data-module.html b/gui/src/data-module.html deleted file mode 100644 index 30e6605d..00000000 --- a/gui/src/data-module.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - Send & Receive Data - - -
-
-
-
-
-
Select data
-
-
- - -
-
-
-
- -
- -
-
-
-
Transmission
-
-
-
-
- - - ACK - 0000 km - 0 dB -
-
-
-
-
-
- Mode - -
-
-
-
- Frames - -
-
-
-
- - -
-
-
-
-
- -
- -
-
-
-
-
Info
-
123
-
-
-
-
-
-
-
-
- -
- -
- - - - - diff --git a/gui/src/hamlib-list.html b/gui/src/hamlib-list.html deleted file mode 100644 index 50be5469..00000000 --- a/gui/src/hamlib-list.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gui/src/img/favicon.png b/gui/src/img/favicon.png deleted file mode 100644 index c6b23789..00000000 Binary files a/gui/src/img/favicon.png and /dev/null differ diff --git a/gui/src/img/icon.png b/gui/src/img/icon.png deleted file mode 100644 index 2e346c91..00000000 Binary files a/gui/src/img/icon.png and /dev/null differ diff --git a/gui/src/index.html b/gui/src/index.html deleted file mode 100644 index 20eae1d1..00000000 --- a/gui/src/index.html +++ /dev/null @@ -1,3956 +0,0 @@ - - - - - - - - - - - - - - - FreeDATA by DJ2LS - - - - - - -
- - - -
-
-
- -
-
-
-
-
-
-
-
- -
-
- Audio devices -
-
- -
-
-
-
- -
-
- - - - -
-
- - - - -
-
-
- -
-
-
-
-
-
-
- -
-
- Rig control -
-
-
- - - -
- - -
-
- - -
-
-
- -
- -
-
-
-
-
- -
-

- TNC will not utilize rig control and features will be - limited. While functional; it is recommended to configure - hamlib. -

-
- - -
-
-
- Rigctld - Address - - Port - -
- -
- Rigctld - - - - - -
-
-
- -
-
-
- TCI - Address - -
- -
- Port - -
-
-
- -
- VOX: Use rig control mode 'none' -
- HAMLIB locally: configure in settings, then - start/stop service. -
- HAMLIB remotely: Enter IP/Port, connection - happens automatically. -
-
- -
-
-
-
-
-
-
-
-
-
- -
-
- My station -
-
- -
-
-
-
-
-
-
-
- - - - - -
-
-
-
- - - - -
-
-
- -
-
-
-
-
-
-
-
-
- -
-
- Updater -
-
-
-
- -
-
-
- -
- -
-
-
-
-
- - - - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- Audio level -
-
- - -
-
- -
-
-
-
-
-
-
-
-
-
-

- S-Meter (dB) -

-
-
-
-
-
-
-
-
-
-
-
-

- dBFS (Audio Level) -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- Broadcasts -
-
- -
-
-
-
-
-
-
-
- - - - - - - -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- - - - - - -
-
- - -
-
- -
- -
-
-
-
-
- - - - -
-
-
-
-
- -
-
-
-
- -
-
- Heard stations -
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - -
- Time - Frequency DXCallDXGridDistanceTypeSNR (rx/dx)
-
- -
-
-
-
-
-
- -
-
- -
Filetransfer
- -
-
- - - -
-
- - - - - - - - - - - - - - -
TimeDXCallFilename
- -
-
- -
-
-
Transmit Files
- -
-
- -
-
-
-
-
-
DX Station
-
-
-
- -
-
-
- - ACK - 0000 km - 0 dB -
-
-
-
-
-
- -
- -
-
-
- -
-
- -
- -
-
-
-
Mode
-
-
-
-
- Mode - -
-
-
-
- Frames - -
-
-
-
-
- -
- -
-
-
- -
-
- -
-
-
-
-
- -
- - -
-
-
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gui/src/js/chatHandler.ts b/gui/src/js/chatHandler.ts new file mode 100644 index 00000000..e2782f24 --- /dev/null +++ b/gui/src/js/chatHandler.ts @@ -0,0 +1,1006 @@ +const path = require("path"); + +const { v4: uuidv4 } = require("uuid"); + +// pinia store setup +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); + +import { useChatStore } from "../store/chatStore.js"; +const chat = useChatStore(pinia); + +import { useStateStore } from "../store/stateStore.js"; +const state = useStateStore(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + +import { sendMessage, sendBroadcastChannel } from "./sock.js"; +import { displayToast } from "./popupHandler.js"; + +//const FD = require("./src/js/freedata.js"); +import { btoa_FD } from "./freedata.js"; + +// define default message object +interface Attachment { + content_type: string; + data: string; +} + +interface messageDefaultObject { + command: string; + hmac_signed: boolean; + percent: number; + bytesperminute: number; + is_new: boolean; + _id: string; + timestamp: number; + dxcallsign: string; + dxgrid: string; + msg: string; + checksum: string; + type: string; + status: string; + attempt: number; + uuid: string; + duration: number; + nacks: number; + speed_list: string; + broadcast_sender?: string; // optional for broadcasts + + _attachments: { + [filename: string]: Attachment; + }; +} + +interface beaconDefaultObject { + command: string; + is_new: boolean; + _id: string; + timestamp: number; + dxcallsign: string; + dxgrid: string; + type: string; + status: string; + uuid: string; + snr: string; +} + +// ---- MessageDB +try { + var PouchDB = require("pouchdb"); +} catch (err) { + console.log(err); + /* + This is a fix for raspberryPi where we get an error when loading pouchdb because of + leveldown package isnt running on ARM devices. + pouchdb-browser does not depend on leveldb and seems to be working. + */ + console.log("using pouchdb-browser fallback"); + var PouchDB = require("pouchdb-browser"); +} + +PouchDB.plugin(require("pouchdb-find")); +//PouchDB.plugin(require('pouchdb-replication')); +PouchDB.plugin(require("pouchdb-upsert")); + +// https://stackoverflow.com/a/26227660 +if (typeof process.env["APPDATA"] !== "undefined") { + var appDataFolder = process.env["APPDATA"]; + console.log(appDataFolder); +} else { + var appDataFolder: string; + + switch (process.platform) { + case "darwin": + appDataFolder = process.env["HOME"] + "/Library/Application Support"; + console.log(appDataFolder); + break; + case "linux": + appDataFolder = process.env["HOME"] + "/.config"; + console.log(appDataFolder); + break; + case "win32": + appDataFolder = "undefined"; + break; + default: + appDataFolder = "undefined"; + break; + } +} +console.log("loading chat database..."); +console.log("appdata folder:" + appDataFolder); +var configFolder = path.join(appDataFolder, "FreeDATA"); +console.log("config folder:" + configFolder); + +var chatDB = path.join(configFolder, "chatDB"); +console.log("database path:" + chatDB); + +var db = new PouchDB(chatDB); + +/* -------- CREATE DATABASE INDEXES */ +//These aren't needed anylonger aslong as we await createIndex() where necessary +//createChatIndex(); + +/* -------- RUN A DATABASE CLEANUP ON STARTUP */ +//dbClean() + +updateAllChat(true); + +// create callsign set for storing unique callsigns +chat.callsign_list = new Set(); + +// function for creating a new broadcast +export function newBroadcast(broadcastChannel, chatmessage) { + var file = ""; + var filetype = "text"; + var filename = ""; + + var file_checksum = ""; //crc32(file).toString(16).toUpperCase(); + var message_type = "broadcast_transmit"; + + var timestamp = Math.floor(Date.now() / 1000); + var uuid = uuidv4(); + // TODO: Not sure what this uuid part is needed for ... + let uuidlast = uuid.lastIndexOf("-"); + uuidlast += 1; + if (uuidlast > 0) { + uuid = uuid.substring(uuidlast); + } + // slice uuid for reducing overhead + uuid = uuid.slice(-4); + + let newChatObj: messageDefaultObject = { + command: "broadcast", + hmac_signed: false, + percent: 0, + bytesperminute: 0, // You need to assign a value here + is_new: false, + _id: uuid, + timestamp: timestamp, + dxcallsign: broadcastChannel, + dxgrid: "null", + msg: chatmessage, + checksum: file_checksum, + type: message_type, + status: "transmitting", + attempt: 1, + uuid: uuid, + duration: 0, + nacks: 0, + speed_list: "null", + _attachments: { + [filename]: { + content_type: filetype, + data: btoa_FD(file), + }, + }, + }; + + //sendMessage(newChatObj) + sendBroadcastChannel(newChatObj); + + addObjToDatabase(newChatObj); +} + +// function for creating a new message +export function newMessage( + dxcallsign, + chatmessage, + chatFile, + chatFileName, + chatFileSize, + chatFileType, +) { + var filename = ""; + var filetype = ""; + var file = ""; + if (typeof chatFile !== "undefined") { + file = chatFile; + filetype = chatFileType; + filename = chatFileName; + } else { + file = ""; + filetype = "text"; + filename = ""; + } + var file_checksum = ""; //crc32(file).toString(16).toUpperCase(); + var message_type = "transmit"; + var command = "msg"; + + var timestamp = Math.floor(Date.now() / 1000); + var uuid = uuidv4(); + // TODO: Not sure what this uuid part is needed for ... + let uuidlast = uuid.lastIndexOf("-"); + uuidlast += 1; + if (uuidlast > 0) { + uuid = uuid.substring(uuidlast); + } + // slice uuid for reducing overhead + uuid = uuid.slice(-8); + + let newChatObj: messageDefaultObject = { + command: command, + hmac_signed: false, + percent: 0, + bytesperminute: 0, // You need to assign a value here + is_new: false, + _id: uuid, + timestamp: timestamp, + dxcallsign: dxcallsign, + dxgrid: "null", + msg: chatmessage, + checksum: file_checksum, + type: message_type, + status: "transmitting", + attempt: 1, + uuid: uuid, + duration: 0, + nacks: 0, + speed_list: "null", + _attachments: { + [filename]: { + content_type: filetype, + data: btoa_FD(file), + }, + }, + }; + + sendMessage(newChatObj); + addObjToDatabase(newChatObj); +} + +// function for creating a list, accessible by callsign +function sortChatList() { + // Create an empty object to store the reordered data dynamically + var reorderedData = {}; + var jsonObjects = chat.unsorted_chat_list; + // Iterate through the list of JSON objects and reorder them dynamically + jsonObjects.forEach((obj) => { + var dxcallsign = obj.dxcallsign; + if (dxcallsign) { + if (!reorderedData[dxcallsign]) { + reorderedData[dxcallsign] = []; + } + reorderedData[dxcallsign].push(obj); + + reorderedData[dxcallsign] = reorderedData[dxcallsign].sort( + sortByProperty("timestamp"), + ); + } + }); + //console.log(reorderedData["2LS-0"]) + return reorderedData; +} + +//https://medium.com/@asadise/sorting-a-json-array-according-one-property-in-javascript-18b1d22cd9e9 +function sortByProperty(property) { + return function (a, b) { + if (a[property] > b[property]) return 1; + else if (a[property] < b[property]) return -1; + + return 0; + }; +} + +export function getMessageAttachment(id) { + return new Promise(async (resolve, reject) => { + try { + const findResult = await db.find({ + selector: { + _id: id, + }, + }); + + const getResult = await db.get(findResult.docs[0]._id, { + attachments: true, + }); + + let obj = getResult; + let filename = Object.keys(obj._attachments)[0]; + let filetype = obj._attachments[filename].content_type; + let file = obj._attachments[filename].data; + resolve([filename, filetype, file]); + } catch (err) { + console.log(err); + reject(false); // Reject the Promise if there's an error + } + }); +} + +//repeat a message +export function repeatMessageTransmission(id) { + // 1. get message object by ID + // 2. Upsert Attempts + // 3. send message + + db.find({ + selector: { + _id: id, + }, + }) + .then(function (result) { + console.log(result); + let obj = result.docs[0]; + console.log(obj); + obj.attempt += 1; + databaseUpsert(obj.uuid, "attempt", obj.attempt); + updateUnsortedChatListEntry(obj.uuid, "attempt", obj.attempt); + sendMessage(obj); + }) + .catch(function (err) { + console.log(err); + }); +} + +// delete a message from databse and gui +export function deleteMessageFromDB(id) { + console.log("deleting: " + id); + db.get(id).then(function (doc) { + db.remove(doc); + }); + + // overwrote unsorted chat list by filtering if not ID + chat.unsorted_chat_list = chat.unsorted_chat_list.filter( + (entry) => entry.uuid !== id, + ); + + // and finally generate our sorted chat list, which is the key store for chat gui rendering + // the removed entry should be removed now from gui + chat.sorted_chat_list = sortChatList(); +} + +//Function to clean old beacons and optimize database +async function dbClean() { + //Only keep the most x latest days of beacons + let beaconKeep = 4; + let itemCount = 0; + let timestampPurge = Math.floor( + (Date.now() - beaconKeep * 24 * 60 * 60 * 1000) / 1000, + ); + + //Items to purge from database + var purgeFilter = [ + { type: "beacon" }, + { type: "ping-ack" }, + { type: "ping" }, + { type: "request" }, + { type: "response" }, + ]; + + await db + .find({ + selector: { + $and: [{ timestamp: { $lt: timestampPurge } }, { $or: purgeFilter }], + }, + }) + .then(async function (result) { + //console.log("Purging " + result.docs.length + " beacons received before " + timestampPurge); + itemCount = result.docs.length; + result.docs.forEach(async function (item) { + await deleteMessageFromDB(item._id); + }); + }) + .catch(function (err) { + console.log(err); + }); + + //Compact database + //Too slow on older/slower machines + //await db.compact(); + + let message = "Database maintenance is complete"; + displayToast("info", "bi bi-info-circle", message, 5000); + + message = "Removed " + itemCount + " items from database"; + displayToast("info", "bi bi-info-circle", message, 5000); +} + +// function to update transmission status +export function updateTransmissionStatus(obj) { + // update database entries + databaseUpsert(obj.uuid, "percent", obj.percent); + databaseUpsert(obj.uuid, "bytesperminute", obj.bytesperminute); + databaseUpsert(obj.uuid, "status", obj.status); + + // update screen rendering / messages + updateUnsortedChatListEntry(obj.uuid, "percent", obj.percent); + updateUnsortedChatListEntry(obj.uuid, "bytesperminute", obj.bytesperminute); + updateUnsortedChatListEntry(obj.uuid, "status", obj.status); +} + +export function updateUnsortedChatListEntry(uuid, object, value) { + var data = getFromUnsortedChatListByUUID(uuid); + if (data) { + data[object] = value; + console.log("Entry updated:", data[object]); + chat.sorted_chat_list = sortChatList(); + return data; + } + + /* + for (const entry of chat.unsorted_chat_list) { + if (entry.uuid === uuid) { + entry[object] = value; + console.log("Entry updated:", entry[object]); + chat.sorted_chat_list = sortChatList(); + return entry; + } + } + */ + + console.log("Entry not updated:", object); + return null; // Return null if not found +} + +function getFromUnsortedChatListByUUID(uuid) { + for (const entry of chat.unsorted_chat_list) { + if (entry.uuid === uuid) { + return entry; + } + } + return false; +} + +export function getNewMessagesByDXCallsign(dxcallsign): [number, number, any] { + let new_counter = 0; + let total_counter = 0; + let item_array = []; + if (typeof dxcallsign !== "undefined") { + for (const key in chat.sorted_chat_list[dxcallsign]) { + //console.log(chat.sorted_chat_list[dxcallsign][key]) + //item_array.push(chat.sorted_chat_list[dxcallsign][key]) + if (chat.sorted_chat_list[dxcallsign][key].is_new) { + item_array.push(chat.sorted_chat_list[dxcallsign][key]); + new_counter += 1; + } + total_counter += 1; + } + } + + return [total_counter, new_counter, item_array]; +} + +export function resetIsNewMessage(uuid, value) { + databaseUpsert(uuid, "is_new", value); + updateUnsortedChatListEntry(uuid, "is_new", value); +} + +export function databaseUpsert(id, object, value) { + db.upsert(id, function (doc) { + if (!doc[object]) { + doc[object] = value; + } + doc[object] = value; + return doc; + }) + .then(function (res) { + // success, res is {rev: '1-xxx', updated: true, id: 'myDocId'} + console.log(res); + }) + .catch(function (err) { + // error + console.log(err); + }); +} + +// function for fetching all messages from chat / updating chat +export async function updateAllChat(cleanup) { + // run cleanup if requested + if (cleanup) { + await dbClean(); + } + let indexFields = [{ dxcallsign: "asc" }, { timestamp: "asc" }]; + let filter = { + selector: { + $and: [ + { dxcallsign: { $exists: true } }, + { timestamp: { $exists: true } }, + //{ $or: chat.chat_filter }, + ], + }, + sort: [{ dxcallsign: "asc" }, { timestamp: "asc" }], + }; + //"{ dxcallsign: \"asc\" }, { timestamp: \"asc\" }" + await createIndex(indexFields); + getFromDBByFilter(filter) + .then(function (result) { + for (var item of (result as any).docs) { + const dxcallsign = item.dxcallsign; + // Check if dxcallsign already exists as a property in the result object + if (!chat.sorted_beacon_list[dxcallsign]) { + // If not, initialize it with an empty array for snr values + chat.sorted_beacon_list[dxcallsign] = { + dxcallsign, + snr: [], + timestamp: [], + }; + chat.callsign_list.add(dxcallsign); + } + + if (item.type === "beacon") { + //console.log(item); + + // TODO: sort beacon list .... maybe a part for a separate function + const jsonData = [item]; + + // Process each JSON item step by step + jsonData.forEach((jsonitem) => { + const { snr, timestamp } = item; + + // Push the snr value to the corresponding dxcallsign's snr array + chat.sorted_beacon_list[dxcallsign].snr.push(snr); + chat.sorted_beacon_list[dxcallsign].timestamp.push(timestamp); + }); + } else { + chat.unsorted_chat_list.push(item); + chat.sorted_chat_list = sortChatList(); + } + } + }) + .catch(function (err) { + console.log(err); + }); +} + +function addObjToDatabase(newobj) { + console.log(newobj); + /* + db.upsert(newobj._id, function (doc) { + if (!doc._id) { + console.log("upsert") + console.log(doc) + doc = newobj + } else { + console.log("new...") + */ + db.post(newobj) + .then(function (response) { + // handle response + console.log("new database entry"); + console.log(response); + + if (newobj.command === "msg") { + chat.unsorted_chat_list.push(newobj); + chat.sorted_chat_list = sortChatList(); + } + }) + .catch(function (err) { + console.log(err); + console.log(newobj); + + // try upserting status in case we tried sending a message to our selfes + databaseUpsert(newobj.uuid, "status", newobj.status); + updateUnsortedChatListEntry(newobj.uuid, "status", newobj.status); + }); + + /* +// upsert footer ... + + } + return doc; + }) +*/ +} + +/* +function createChatIndex() { + db.createIndex({ + index: { + fields: [ + "timestamp", + "uuid", + "dxcallsign", + "dxgrid", + "msg", + "checksum", + "type", + "command", + "status", + "percent", + "attempt", + "hmac_signed", + "bytesperminute", + "_attachments", + "is_new", + "nacks", + "duration", + "speed_list", + ], + }, + }) + .then(function (result) { + // handle result + console.log(result); + }) + .catch(function (err) { + console.log(err); + }); +} +*/ + +export function deleteChatByCallsign(callsign) { + chat.callsign_list.delete(callsign); + // @ts-expect-error + delete chat.unsorted_chat_list.callsign; + delete chat.sorted_chat_list.callsign; + + deleteFromDatabaseByCallsign(callsign); +} + +function deleteFromDatabaseByCallsign(callsign) { + db.find({ + selector: { + dxcallsign: callsign, + }, + }) + .then(function (result) { + // handle result + if (typeof result !== "undefined") { + result.docs.forEach(function (item) { + console.log(item); + db.get(item._id) + .then(function (doc) { + db.remove(doc) + .then(function () { + updateAllChat(false); + return true; + }) + .catch(function (err) { + console.log(err); + }); + }) + .catch(function (err) { + console.log(err); + }); + }); + } + }) + .catch(function (err) { + console.log(err); + }); +} + +// function for handling a received beacon +export function newBeaconReceived(obj) { + /* +{ + "freedata": "modem-message", + "beacon": "received", + "uuid": "12741312-3dbb-4a53-b0cc-100f6c930ab8", + "timestamp": 1696076869, + "dxcallsign": "DJ2LS-0", + "dxgrid": "JN48CS", + "snr": "-2.8", + "mycallsign": "DJ2LS-0" +} +*/ + let newChatObj: beaconDefaultObject = { + command: "beacon", + is_new: false, + _id: obj["uuid"], + timestamp: obj["timestamp"], + dxcallsign: obj["dxcallsign"], + dxgrid: obj["dxgrid"], + type: "beacon", + status: obj["beacon"], + uuid: obj["uuid"], + snr: obj["snr"], // adding the new field + }; + + addObjToDatabase(newChatObj); + + console.log(obj); + + const jsonData = [obj]; + const dxcallsign = obj.dxcallsign; + // Process each JSON item step by step + jsonData.forEach((item) => { + const { snr, timestamp } = obj; + + // Check if dxcallsign already exists as a property in the result object + if (!chat.sorted_beacon_list[dxcallsign]) { + // If not, initialize it with an empty array for snr values + chat.sorted_beacon_list[dxcallsign] = { + dxcallsign, + snr: [], + timestamp: [], + }; + } + + // Push the snr value to the corresponding dxcallsign's snr array + chat.sorted_beacon_list[dxcallsign].snr.push(snr); + chat.sorted_beacon_list[dxcallsign].timestamp.push(timestamp); + }); + + // check if auto retry enabled + console.log("-----------------------------------------"); + console.log(settings.enable_auto_retry.toUpperCase()); + if (settings.enable_auto_retry.toUpperCase() == "TRUE") { + checkForWaitingMessages(dxcallsign); + } +} + +// function for handling a received message +export function newMessageReceived(message, protocol) { + /* + + PROTOCOL +{ + "freedata": "modem-message", + "arq": "transmission", + "status": "received", + "uuid": "5a3caa57-7feb-4436-853d-e341b085350f", + "percent": 100, + "bytesperminute": 206, + "compression": 0.5833333333333334, + "timestamp": 1697048385, + "finished": 0, + "mycallsign": "DJ2LS-0", + "dxcallsign": "DJ2LS-0", + "dxgrid": "------", + "data": "bTA7MTttc2cwOzE7MDsxOzBlNGE3YjQ2MDsxOzE2OTcwNDgzMTkwOzE7dGVzdDMwOzE7MDsxO3RleHQwOzE7", + "irs": "False", + "hmac_signed": "False", + "duration": 44.385897636413574, + "nacks": 1, + "speed_list": [ + { + "snr": 0, + "bpm": 106, + "timestamp": 1697048362 + }, + { + "snr": -6, + "bpm": 104, + "timestamp": 1697048370 + }, + { + "snr": -6, + "bpm": 81, + "timestamp": 1697048370 + }, + { + "snr": -5.7, + "bpm": 161, + "timestamp": 1697048378 + }, + { + "snr": -5.7, + "bpm": 133, + "timestamp": 1697048379 + }, + { + "snr": -5.4, + "bpm": 206, + "timestamp": 1697048385 + }, + { + "snr": -5.8, + "bpm": 179, + "timestamp": 1697048391 + } + ] +} + + MESSAGE; decoded from "data" + [ + 0 - protocol type message - "m", + 1 - type - "msg", + 2 - checksum "", + 3 - uuid - "07e2", + 4 - timestamp - "1695203833", + 5 - message - "test", + 6 - file name - "", + 7 - mime - "plain/text", + 8 - file - "" + ] + + + */ + console.log(protocol); + + let newChatObj: messageDefaultObject = { + command: "msg", + hmac_signed: protocol["hmac_signed"], + percent: 100, + bytesperminute: protocol["bytesperminute"], + is_new: true, + _id: message[3], + timestamp: message[4], + dxcallsign: protocol["dxcallsign"], + dxgrid: protocol["dxgrid"], + msg: message[5], + checksum: message[2], + type: protocol["status"], + status: protocol["status"], + attempt: 1, + uuid: message[3], + duration: protocol["duration"], + nacks: protocol["nacks"], + speed_list: protocol["speed_list"], + _attachments: { + [message[6]]: { + content_type: message[7], + data: btoa_FD(message[8]), + }, + }, + }; + + // some tweaks for broadcasts + if (protocol.fec == "broadcast") { + newChatObj.broadcast_sender = protocol["dxcallsign"]; + newChatObj.type = "broadcast_received"; + } + + addObjToDatabase(newChatObj); +} + +export function setStateFailed() { + state.arq_seconds_until_finish = 0; + state.arq_seconds_until_timeout = 180; + state.arq_seconds_until_timeout_percent = 100; +} + +export function setStateSuccess() { + state.arq_seconds_until_finish = 0; + state.arq_seconds_until_timeout = 180; + state.arq_seconds_until_timeout_percent = 100; +} + +export function requestMessageInfo(id) { + console.log(id); + + chat.arq_speed_list_bpm = []; + chat.arq_speed_list_timestamp = []; + chat.arq_speed_list_snr = []; + //@ts-expect-error + chat.selectedMessageObject = []; + + // id and uuid are the same + var data = getFromUnsortedChatListByUUID(id); + chat.selectedMessageObject = data; + + if ( + typeof data["speed_list"] !== "undefined" && + data["speed_list"].length > 0 + ) { + prepareStatsDataForStore(data["speed_list"]); + } else { + prepareStatsDataForStore([{}]); + } + + return; +} + +// THis is a nearly duplicate of the same function in sock.js :-( +function prepareStatsDataForStore(data) { + // dummy data + //state.arq_speed_list = [{"snr":0.0,"bpm":104,"timestamp":1696189769},{"snr":0.0,"bpm":80,"timestamp":1696189778},{"snr":0.0,"bpm":70,"timestamp":1696189783},{"snr":0.0,"bpm":58,"timestamp":1696189792},{"snr":0.0,"bpm":52,"timestamp":1696189797},{"snr":"NaN","bpm":42,"timestamp":1696189811},{"snr":0.0,"bpm":22,"timestamp":1696189875},{"snr":0.0,"bpm":21,"timestamp":1696189881},{"snr":0.0,"bpm":17,"timestamp":1696189913},{"snr":0.0,"bpm":15,"timestamp":1696189932},{"snr":0.0,"bpm":15,"timestamp":1696189937},{"snr":0.0,"bpm":14,"timestamp":1696189946},{"snr":-6.1,"bpm":14,"timestamp":1696189954},{"snr":-6.1,"bpm":14,"timestamp":1696189955},{"snr":-5.5,"bpm":28,"timestamp":1696189963},{"snr":-5.5,"bpm":27,"timestamp":1696189963}] + console.log(data); + console.log(); + var speed_listSize = 0; + if (typeof data == "undefined") { + speed_listSize = 0; + } else { + speed_listSize = data.length; + } + + var speed_list_bpm = []; + + for (let i = 0; i < speed_listSize; i++) { + speed_list_bpm.push(data[i].bpm); + } + + var speed_list_timestamp = []; + + for (let i = 0; i < speed_listSize; i++) { + let timestamp = data[i].timestamp * 1000; + let h = new Date(timestamp).getHours(); + let m = new Date(timestamp).getMinutes(); + let s = new Date(timestamp).getSeconds(); + let time = h + ":" + m + ":" + s; + speed_list_timestamp.push(time); + } + + var speed_list_snr = []; + for (let i = 0; i < speed_listSize; i++) { + let snr = NaN; + if (data[i].snr !== 0) { + snr = data[i].snr; + } else { + snr = NaN; + } + speed_list_snr.push(snr); + } + + chat.arq_speed_list_bpm = speed_list_bpm; + chat.arq_speed_list_timestamp = speed_list_timestamp; + chat.arq_speed_list_snr = speed_list_snr; + + return; +} + +async function createIndex(myIndexFields) { + db.createIndex({ + index: { + fields: myIndexFields, + }, + }).catch((err) => { + console.log(err); + }); +} + +async function getFromDBByFilter(filter) { + /* +USAGE: + +let filter = { + selector: { + dxcallsign: dxcall, + type: "transmit", + status: "failed", + //attempt: { $lt: parseInt(config.max_retry_attempts) } + }, + } + +getFromDBByFilter(filter) + .then(result => { + console.log(result) + }) + .catch(err => { + console.log(err) + }); + +*/ + return new Promise((resolve, reject) => { + return db + .find(filter) + .then((result) => { + //console.log(result); + resolve(result); + }) + .catch((err) => { + console.log(err); + reject(err); + }); + }); +} + +async function checkForWaitingMessages(dxcall) { + let filter = { + selector: { + dxcallsign: dxcall, + type: "transmit", + status: "failed", + //attempt: { $lt: parseInt(config.max_retry_attempts) } + }, + }; + + getFromDBByFilter(filter) + .then((result) => { + let message = + // @ts-expect-error + "Found " + result.docs.length + " waiting messages for " + dxcall; + + console.log(message); + displayToast("info", "bi bi-info-circle", message, 5000); + + // handle result + // @ts-expect-error + if (result.docs.length > 0) { + // only want to process the first available item object, then return + // this ensures, we are only sending one message at once + // @ts-expect-error + if (result.docs[0].attempt < settings.max_retry_attempts) { + // @ts-expect-error + repeatMessageTransmission(result.docs[0].uuid); + } + return; + } + }) + .catch((err) => { + console.log(err); + }); +} diff --git a/gui/src/js/daemon.ts b/gui/src/js/daemon.ts new file mode 100644 index 00000000..197bf25c --- /dev/null +++ b/gui/src/js/daemon.ts @@ -0,0 +1,281 @@ +//var net = require("net"); +var net = require("node:net"); + +// ----------------- init pinia stores ------------- +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); +import { useAudioStore } from "../store/audioStore.js"; +const audioStore = useAudioStore(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + +import { useStateStore } from "../store/stateStore.js"; +const state = useStateStore(pinia); + +var daemon = new net.Socket(); +var socketchunk = []; // Current message, per connection. + +// global to keep track of daemon connection error emissions +var daemonShowConnectStateError = 1; + +setTimeout(connectDAEMON, 500); + +function connectDAEMON() { + if (daemonShowConnectStateError == 1) { + console.log("connecting to daemon"); + } + + //clear message buffer after reconnecting or initial connection + socketchunk = []; + + daemon.connect(settings.daemon_port, settings.daemon_host); + + //client.setTimeout(5000); +} + +daemon.on("connect", function () { + console.log("daemon connection established"); + daemonShowConnectStateError = 1; +}); + +daemon.on("error", function (err) { + if (daemonShowConnectStateError == 1) { + console.log("daemon connection error"); + console.log("Make sure the daemon is started."); + console.log('Run "python daemon.py" in the modem directory.'); + console.log(err); + daemonShowConnectStateError = 0; + } + setTimeout(connectDAEMON, 500); + daemon.destroy(); +}); + +/* +client.on('close', function(data) { + console.log(' Modem connection closed'); + setTimeout(connectModem, 2000) + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send('request-update-daemon-connection', Data); +}); +*/ + +daemon.on("end", function (data) { + console.log("daemon connection ended"); + console.log(data); + daemon.destroy(); + setTimeout(connectDAEMON, 500); +}); + +//exports.writeDaemonCommand = function(command){ +//writeDaemonCommand = function (command) { +function writeDaemonCommand(command) { + // we use the writingCommand function to update our TCPIP state because we are calling this function a lot + // if socket opened, we are able to run commands + if (daemon.readyState == "open") { + //uiMain.setDAEMONconnection('open') + daemon.write(command + "\n"); + } + + if (daemon.readyState == "closed") { + //uiMain.setDAEMONconnection('closed') + } + + if (daemon.readyState == "opening") { + //uiMain.setDAEMONconnection('opening') + } +} + +// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in" + +daemon.on("data", function (socketdata) { + /* + inspired by: + stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in + */ + + socketdata = socketchunk.join("\n") + socketdata.toString("utf8"); //append incoming data to socketchunk + //socketdata = socketdata.toString("utf8"); // convert data to string + + //socketchunk += socketdata; // append data to buffer so we can stick long data together + + // check if we received begin and end of json data + if (socketdata.startsWith('{"') && socketdata.endsWith('"}\n')) { + var data = ""; + + // split data into chunks if we received multiple commands + socketchunk = socketdata.split("\n"); + data = JSON.parse(socketchunk[0]); + + // search for empty entries in socketchunk and remove them + for (var i = 0; i < socketchunk.length; i++) { + if (socketchunk[i] === "") { + socketchunk.splice(i, 1); + } + } + + //iterate through socketchunks array to execute multiple commands in row + for (i = 0; i < socketchunk.length; i++) { + //check if data is not empty + if (socketchunk[i].length > 0) { + //try to parse JSON + try { + data = JSON.parse(socketchunk[i]); + } catch (e) { + console.log(e); // "SyntaxError + //daemonLog.debug(socketchunk[i]); + socketchunk = []; + } + } + + console.log(data); + if (data["command"] == "daemon_state") { + // update audio devices by putting them to audio store + audioStore.inputDevices = data["input_devices"]; + audioStore.outputDevices = data["output_devices"]; + settings.serial_devices = data["serial_devices"]; + state.python_version = data["python_version"]; + state.modem_version = data["version"]; + state.modem_running_state = data["daemon_state"][0]["status"]; + state.rigctld_started = data["rigctld_state"][0]["status"]; + //state.rigctld_process = data["daemon_state"][0]["rigctld_process"]; + } + + if (data["command"] == "test_hamlib") { + // + } + } + + //finally delete message buffer + socketchunk = []; + } +}); + +// START Modem +// ` `== multi line string +export function startModem() { + var json_command = JSON.stringify({ + type: "set", + command: "start_modem", + parameter: [ + { + mycall: settings.mycall, + mygrid: settings.mygrid, + rx_audio: audioStore.startupInputDevice, + tx_audio: audioStore.startupOutputDevice, + radiocontrol: settings.radiocontrol, + devicename: settings.hamlib_deviceid, + deviceport: settings.hamlib_deviceport, + pttprotocol: settings.hamlib_pttprotocol, + pttport: settings.hamlib_ptt_port, + serialspeed: settings.hamlib_serialspeed, + data_bits: settings.hamlib_data_bits, + stop_bits: settings.hamlib_stop_bits, + handshake: settings.hamlib_handshake, + rigctld_port: settings.hamlib_rigctld_port, + rigctld_ip: settings.hamlib_rigctld_ip, + enable_scatter: settings.enable_scatter, + enable_fft: settings.enable_fft, + enable_fsk: settings.enable_fsk, + low_bandwidth_mode: settings.low_bandwidth_mode, + tuning_range_fmin: settings.tuning_range_fmin, + tuning_range_fmax: settings.tuning_range_fmax, + //tx_audio_level: settings.tx_audio_level, + respond_to_cq: settings.respond_to_cq, + rx_buffer_size: settings.rx_buffer_size, + enable_explorer: settings.enable_explorer, + enable_stats: settings.explorer_stats, + enable_auto_tune: settings.auto_tune, + tx_delay: settings.tx_delay, + tci_ip: settings.tci_ip, + tci_port: settings.tci_port, + enable_mesh: settings.enable_mesh_features, + }, + ], + }); + + console.log(json_command); + writeDaemonCommand(json_command); +} + +// STOP Modem +//exports.stopModem = function () { +export function stopModem() { + var command = + '{"type" : "set", "command": "stop_modem" , "parameter": "---" }'; + writeDaemonCommand(command); +} + +// TEST HAMLIB +function testHamlib( + //exports.testHamlib = function ( + radiocontrol, + devicename, + deviceport, + serialspeed, + pttprotocol, + pttport, + data_bits, + stop_bits, + handshake, + rigctld_ip, + rigctld_port, +) { + var json_command = JSON.stringify({ + type: "get", + command: "test_hamlib", + parameter: [ + { + radiocontrol: radiocontrol, + devicename: devicename, + deviceport: deviceport, + pttprotocol: pttprotocol, + pttport: pttport, + serialspeed: serialspeed, + data_bits: data_bits, + stop_bits: stop_bits, + handshake: handshake, + rigctld_port: rigctld_port, + rigctld_ip: rigctld_ip, + }, + ], + }); + console.log(json_command); + writeDaemonCommand(json_command); +} + +export function startRigctld() { + var json_command = JSON.stringify({ + type: "set", + command: "start_rigctld", + parameter: [ + { + hamlib_deviceid: settings.hamlib_deviceid, + hamlib_deviceport: settings.hamlib_deviceport, + hamlib_stop_bits: settings.hamlib_stop_bits, + hamlib_data_bits: settings.hamlib_data_bits, + hamlib_handshake: settings.hamlib_handshake, + hamlib_serialspeed: settings.hamlib_serialspeed, + hamlib_dtrstate: settings.hamlib_dtrstate, + hamlib_pttprotocol: settings.hamlib_pttprotocol, + hamlib_ptt_port: settings.hamlib_ptt_port, + hamlib_dcd: settings.hamlib_dcd, + hamlbib_serialspeed_ptt: settings.hamlib_serialspeed, + hamlib_rigctld_port: settings.hamlib_rigctld_port, + hamlib_rigctld_ip: settings.hamlib_rigctld_ip, + hamlib_rigctld_path: settings.hamlib_rigctld_path, + hamlib_rigctld_server_port: settings.hamlib_rigctld_server_port, + hamlib_rigctld_custom_args: settings.hamlib_rigctld_custom_args, + }, + ], + }); + console.log(json_command); + writeDaemonCommand(json_command); +} +export function stopRigctld() { + let command = '{"type" : "set", "command": "stop_rigctld"}'; + writeDaemonCommand(command); +} diff --git a/gui/preload-chat.js b/gui/src/js/deprecated_preload-chat.js similarity index 97% rename from gui/preload-chat.js rename to gui/src/js/deprecated_preload-chat.js index a7cf6d18..b0b3ca75 100644 --- a/gui/preload-chat.js +++ b/gui/src/js/deprecated_preload-chat.js @@ -37,7 +37,7 @@ const dateFormatHours = new Intl.DateTimeFormat(navigator.language, { hour12: false, }); // split character -const split_char = "\0;\1;"; +//const split_char = "\0;\1;"; // global for our selected file we want to transmit // ----------------- some chat globals var filetype = ""; @@ -80,6 +80,8 @@ PouchDB.plugin(require("pouchdb-find")); //PouchDB.plugin(require('pouchdb-replication')); PouchDB.plugin(require("pouchdb-upsert")); + + var db = new PouchDB(chatDB); var users = new PouchDB(userDB); @@ -150,7 +152,8 @@ var chatFilter = [ window.addEventListener("DOMContentLoaded", () => { updateAllChat(false); // theme selector - changeGuiDesign(config.theme); + // TODO: Remove for one pager, also remove function! + //changeGuiDesign(config.theme); const userInfoFields = [ "user_info_image", @@ -276,7 +279,8 @@ window.addEventListener("DOMContentLoaded", () => { .then(function (doc) { db.remove(doc) .then(function (doc) { - return location.reload(); + updateAllChat(true); + return true; }) .catch(function (err) { console.log(err); @@ -343,7 +347,7 @@ window.addEventListener("DOMContentLoaded", () => { lines = 6; } } - var message_container_height_offset = 130 + 20 * lines; + var message_container_height_offset = 180 + 20 * lines; var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = message_container_height; @@ -373,7 +377,7 @@ window.addEventListener("DOMContentLoaded", () => { "bi bi-chevron-compact-up"; } - var message_container_height_offset = 130 + 20 * lines; + var message_container_height_offset = 180 + 20 * lines; //var message_container_height_offset = 90 + (23*lines); var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = @@ -456,7 +460,7 @@ window.addEventListener("DOMContentLoaded", () => { //Remove non-printable chars from begining and end of string--should save us a byte here and there chatmessage = chatmessage.toString().trim(); // reset textarea size - var message_container_height_offset = 150; + var message_container_height_offset = 200; var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = message_container_height; @@ -669,24 +673,12 @@ ipcRenderer.on("return-select-user-image", (event, arg) => { }); }); -ipcRenderer.on("action-update-reception-status", (event, arg) => { - var data = arg["data"][0]; - document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign; -}); -ipcRenderer.on("action-clear-reception-status", (event) => { - //Clear connected with textbox - let cwc = document.getElementById("txtConnectedWithChat"); - if (cwc.textContent != "------") { - cwc.textContent = "------"; - //console.log("Reseting connected with"); - } -}); + ipcRenderer.on("action-update-transmission-status", (event, arg) => { var data = arg["data"][0]; - document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign; if (data.status == "opening") return; if (typeof data.uuid === undefined) return; @@ -1064,6 +1056,7 @@ update_chat = function (obj) { var attempt = obj.attempt; } + // add percent and bytes per minute if not existing //console.log(obj.percent) if (typeof obj.percent == "undefined") { @@ -1266,13 +1259,13 @@ update_chat = function (obj) { `; document - .getElementById("list-tab") + .getElementById("list-tab-chat") .insertAdjacentHTML("beforeend", new_callsign); var message_area = `
`; document - .getElementById("nav-tabContent") + .getElementById("nav-tabContent-Chat") .insertAdjacentHTML("beforeend", message_area); // finally get and set user information to first selected item @@ -1392,36 +1385,40 @@ update_chat = function (obj) { showOsPopUp("Message received from " + obj.dxcallsign, obj.msg); } - // check if message is signed or not for adjusting icon - if ( - typeof obj.hmac_signed !== "undefined" && - obj.hmac_signed !== "False" - ) { - console.log(hmac_signed); - var hmac_signed = ''; - } else { - var hmac_signed = ''; - } + + // check if message is signed or not for adjusting icon + if(typeof obj.hmac_signed !== "undefined" && obj.hmac_signed !== "False"){ + console.log(hmac_signed) + var hmac_signed = ''; + } else { + + var hmac_signed = ''; + + } + var new_message = `
- -
+
${fileheader}

${message_html}

- ${timestamp} + ${timestamp}

- + - ${hmac_signed} + ${hmac_signed}
@@ -1438,11 +1435,11 @@ update_chat = function (obj) {
-
+

${message_html}

- ${timestamp} + ${timestamp}

@@ -2252,9 +2249,9 @@ async function updateAllChat(clear) { callsign_counter = 0; //selected_callsign = ""; dxcallsigns.clear(); - document.getElementById("list-tab").innerHTML = ""; - document.getElementById("nav-tabContent").innerHTML = ""; - //document.getElementById("list-tab").childNodes.remove(); + document.getElementById("list-tab-chat").innerHTML = ""; + document.getElementById("nav-tabContent-Chat").innerHTML = ""; + //document.getElementById("list-tab-chat").childNodes.remove(); //document.getElementById("nav-tab-content").childrenNodes.remove(); } //Ensure we create an index before running db.find @@ -2905,6 +2902,8 @@ function clearUnreadMessages(dxcall) { .catch(function (err) { console.log(err); }); + + } //Have the operating system show a notification popup diff --git a/gui/preload-log.js b/gui/src/js/deprecated_preload-log.js similarity index 100% rename from gui/preload-log.js rename to gui/src/js/deprecated_preload-log.js diff --git a/gui/preload-main.js b/gui/src/js/deprecated_preload-main.js similarity index 95% rename from gui/preload-main.js rename to gui/src/js/deprecated_preload-main.js index 7201775c..27e32275 100644 --- a/gui/preload-main.js +++ b/gui/src/js/deprecated_preload-main.js @@ -1,8 +1,23 @@ const path = require("path"); -const { ipcRenderer, shell, clipboard } = require("electron"); +const { shell, clipboard } = require("electron"); const exec = require("child_process").spawn; -const sock = require("./sock.js"); -const daemon = require("./daemon.js"); + + +//import { useIpcRenderer } from '@vueuse/electron' +//const ipcRenderer = useIpcRenderer() + + +//import * as bootstrap from 'bootstrap' + + +//require("./preload-chat.js"); +//require("./preload-mesh.js"); +//require("./preload-log.js"); + +//const daemon = require("./daemon.js"); +//const sock = require("./sock.js"); + + const fs = require("fs"); const FD = require("./freedata"); const { @@ -398,12 +413,12 @@ window.addEventListener("DOMContentLoaded", () => { } if (config.enable_mesh_features.toLowerCase() == "true") { - document.getElementById("liMeshTable").style.visibility = "visible"; - document.getElementById("liMeshTable").style.display = "block"; + document.getElementById("list-mesh-list").style.visibility = "visible"; + document.getElementById("list-mesh-list").style.display = "block"; document.getElementById("enableMeshSwitch").checked = true; } else { - document.getElementById("liMeshTable").style.visibility = "hidden"; - document.getElementById("liMeshTable").style.display = "none"; + document.getElementById("list-mesh-list").style.visibility = "hidden"; + document.getElementById("list-mesh-list").style.display = "none"; document.getElementById("enableMeshSwitch").checked = false; } @@ -560,7 +575,7 @@ window.addEventListener("DOMContentLoaded", () => { document.getElementById("wftheme_selector").value = config.wftheme; spectrum.setColorMap(config.wftheme); - document.getElementById("btnAbout").addEventListener("click", () => { + document.getElementById("list-info-list").addEventListener("click", () => { document.getElementById("aboutVersion").innerText = appVer; let maxcol = 3; let col = 2; @@ -1258,12 +1273,12 @@ window.addEventListener("DOMContentLoaded", () => { document.getElementById("enableMeshSwitch").addEventListener("click", () => { if (document.getElementById("enableMeshSwitch").checked == true) { config.enable_mesh_features = "True"; - document.getElementById("liMeshTable").style.visibility = "visible"; - document.getElementById("liMeshTable").style.display = "block"; + document.getElementById("list-mesh-list").style.visibility = "visible"; + document.getElementById("list-mesh-list").style.display = "block"; } else { config.enable_mesh_features = "False"; - document.getElementById("liMeshTable").style.visibility = "hidden"; - document.getElementById("liMeshTable").style.display = "none"; + document.getElementById("list-mesh-list").style.visibility = "hidden"; + document.getElementById("list-mesh-list").style.display = "none"; } //fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); FD.saveConfig(config, configPath); @@ -1458,12 +1473,12 @@ window.addEventListener("DOMContentLoaded", () => { if (document.getElementById("enableMeshSwitch").checked == true) { var enable_mesh_features = "True"; - document.getElementById("liMeshTable").style.visibility = "visible"; - document.getElementById("liMeshTable").style.display = "block"; + document.getElementById("list-mesh-list").style.visibility = "visible"; + document.getElementById("list-mesh-list").style.display = "block"; } else { var enable_mesh_features = "False"; - document.getElementById("liMeshTable").style.visibility = "hidden"; - document.getElementById("liMeshTable").style.display = "none"; + document.getElementById("list-mesh-list").style.visibility = "hidden"; + document.getElementById("list-mesh-list").style.display = "none"; } if (document.getElementById("scatterSwitch").checked == true) { @@ -1634,13 +1649,6 @@ window.addEventListener("DOMContentLoaded", () => { ); }); - document.getElementById("tncLog").addEventListener("click", () => { - ipcRenderer.send("request-open-tnc-log"); - }); - - document.getElementById("meshtable").addEventListener("click", () => { - ipcRenderer.send("request-open-mesh-module"); - }); // stopTNC button clicked document.getElementById("stopTNC").addEventListener("click", () => { @@ -1746,13 +1754,7 @@ window.addEventListener("DOMContentLoaded", () => { sock.disconnectARQ(); }); - // OPEN CHAT MODULE - document.getElementById("openRFChat").addEventListener("click", () => { - let Data = { - command: "openRFChat", - }; - ipcRenderer.send("request-show-chat-window", Data); - }); + document.getElementById("thTime").addEventListener("click", () => { if (hslLastSort == 0 && hslLastSortDir == "asc") hslLastSortDir = "desc"; @@ -2015,9 +2017,8 @@ ipcRenderer.on("action-update-tnc-state", (event, arg) => { checkForNewMessageWait = -1; } checkForNewMessageWait++; - // update FFT - if (typeof arg.fft !== "undefined") { - // FIXME: WE need to fix this when disabled waterfall chart + // update FFT only if data available + if (typeof arg.fft !== "undefined" && arg.fft !== "[]") { try { var array = JSON.parse("[" + arg.fft + "]"); spectrum.addData(array[0]); @@ -2757,7 +2758,6 @@ ipcRenderer.on("action-update-hamlib-test", (event, arg) => { //document.getElementById("testHamlibAdvanced").className = "btn btn-sm btn-danger"; } }); - ipcRenderer.on("action-update-daemon-connection", (event, arg) => { if (arg.daemon_connection == "open") { document.getElementById("daemon_connection_state").className = @@ -2775,88 +2775,6 @@ ipcRenderer.on("action-update-daemon-connection", (event, arg) => { //document.getElementById("blurdiv").style.webkitFilter = "blur(10px)"; } }); - -ipcRenderer.on("action-update-tnc-connection", (event, arg) => { - if (arg.tnc_connection == "open") { - /* - document.getElementById('hamlib_deviceid').disabled = true; - document.getElementById('hamlib_deviceport').disabled = true; - document.getElementById('testHamlib').disabled = true; - document.getElementById('hamlib_ptt_protocol').disabled = true; - document.getElementById('audio_input_selectbox').disabled = true; - document.getElementById('audio_output_selectbox').disabled = true; - //document.getElementById('stopTNC').disabled = false; - document.getElementById('startTNC').disabled = true; - document.getElementById('dxCall').disabled = false; - document.getElementById("hamlib_serialspeed").disabled = true; - document.getElementById("openDataModule").disabled = false; - */ - - // collapse settings screen - var collapseFirstRow = new bootstrap.Collapse( - document.getElementById("collapseFirstRow"), - { toggle: false }, - ); - collapseFirstRow.hide(); - var collapseSecondRow = new bootstrap.Collapse( - document.getElementById("collapseSecondRow"), - { toggle: false }, - ); - collapseSecondRow.hide(); - var collapseThirdRow = new bootstrap.Collapse( - document.getElementById("collapseThirdRow"), - { toggle: false }, - ); - collapseThirdRow.show(); - var collapseFourthRow = new bootstrap.Collapse( - document.getElementById("collapseFourthRow"), - { toggle: false }, - ); - collapseFourthRow.show(); - - //Set tuning for fancy graphics mode (high/low CPU) - set_CPU_mode(); - - //GUI will auto connect to TNC if already running, if that is the case increment start count if 0 - if (tncStartCount == 0) tncStartCount++; - } else { - /* - document.getElementById('hamlib_deviceid').disabled = false; - document.getElementById('hamlib_deviceport').disabled = false; - document.getElementById('testHamlib').disabled = false; - document.getElementById('hamlib_ptt_protocol').disabled = false; - document.getElementById('audio_input_selectbox').disabled = false; - document.getElementById('audio_output_selectbox').disabled = false; - //document.getElementById('stopTNC').disabled = true; - document.getElementById('startTNC').disabled = false; - document.getElementById('dxCall').disabled = true; - document.getElementById("hamlib_serialspeed").disabled = false; - document.getElementById("openDataModule").disabled = true; - */ - // collapse settings screen - var collapseFirstRow = new bootstrap.Collapse( - document.getElementById("collapseFirstRow"), - { toggle: false }, - ); - collapseFirstRow.show(); - var collapseSecondRow = new bootstrap.Collapse( - document.getElementById("collapseSecondRow"), - { toggle: false }, - ); - collapseSecondRow.show(); - var collapseThirdRow = new bootstrap.Collapse( - document.getElementById("collapseThirdRow"), - { toggle: false }, - ); - collapseThirdRow.hide(); - var collapseFourthRow = new bootstrap.Collapse( - document.getElementById("collapseFourthRow"), - { toggle: false }, - ); - collapseFourthRow.hide(); - } -}); - ipcRenderer.on("action-update-rx-buffer", (event, arg) => { var data = arg.data["data"]; @@ -2976,11 +2894,11 @@ ipcRenderer.on("run-tnc-command-fec-iswriting", (event) => { ipcRenderer.on("action-update-unread-messages-main", (event, data) => { //Do something if (data == true) { - document.getElementById("openRFChat").classList.add("btn-warning"); - document.getElementById("openRFChat").classList.remove("btn-secondary"); + document.getElementById("list-messages-list").classList.add("bg-warning"); + document.getElementById("list-messages-list").classList.remove("bg-secondary"); } else { - document.getElementById("openRFChat").classList.remove("btn-warning"); - document.getElementById("openRFChat").classList.add("btn-secondary"); + document.getElementById("list-messages-list").classList.remove("bg-warning"); + document.getElementById("list-messages-list").classList.add("bg-secondary"); } }); @@ -3143,7 +3061,6 @@ ipcRenderer.on("action-updater", (event, arg) => { }); // ----------- INFO MODAL ACTIONS ------------------------------- - // CQ TRANSMITTING ipcRenderer.on("action-show-cq-toast-transmitting", (event, data) => { displayToast( diff --git a/gui/preload-mesh.js b/gui/src/js/deprecated_preload-mesh.js similarity index 100% rename from gui/preload-mesh.js rename to gui/src/js/deprecated_preload-mesh.js diff --git a/gui/freedata.js b/gui/src/js/freedata.ts similarity index 53% rename from gui/freedata.js rename to gui/src/js/freedata.ts index 6b02a8d2..6f532bee 100644 --- a/gui/freedata.js +++ b/gui/src/js/freedata.ts @@ -1,37 +1,27 @@ -const fs = require("fs"); -const { ipcRenderer } = require("electron"); - -/** - * Save config and update config setting globally - * @param {string} config - config data - * @param {string} configPath - */ -exports.saveConfig = function (config, configPath) { - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - ipcRenderer.send("set-config-global", config); -}; - /** * Binary to ASCII replacement * @param {string} data in normal/usual utf-8 format * @returns base64 encoded string */ -exports.btoa_FD = function (data) { +export function btoa_FD(data) { + //exports.btoa_FD = function (data) { return Buffer.from(data, "utf-8").toString("base64"); -}; +} /** * ASCII to Binary replacement * @param {string} data in base64 encoding * @returns utf-8 normal/usual string */ -exports.atob_FD = function (data) { +export function atob_FD(data) { + //exports.atob_FD = function (data) { return Buffer.from(data, "base64").toString("utf-8"); -}; +} /** * UTF8 to ASCII btoa * @param {string} data in base64 encoding * @returns base64 bota compatible data for use in browser */ -exports.atob = function (data) { +export function atob(data) { + //exports.atob = function (data) { return window.btoa(Buffer.from(data, "base64").toString("utf8")); -}; +} diff --git a/gui/src/js/popupHandler.ts b/gui/src/js/popupHandler.ts new file mode 100644 index 00000000..a2ea6ec3 --- /dev/null +++ b/gui/src/js/popupHandler.ts @@ -0,0 +1,44 @@ +const { v4: uuidv4 } = require("uuid"); +import * as bootstrap from "bootstrap"; + +export function displayToast(type, icon, content, duration) { + let mainToastContainer = document.getElementById("mainToastContainer"); + + let randomID = uuidv4(); + let toastCode = ` + + `; + + // insert toast to toast container + mainToastContainer.insertAdjacentHTML("beforeend", toastCode); + + // register toast + let toastHTMLElement = document.getElementById(randomID); + let toast = bootstrap.Toast.getOrCreateInstance(toastHTMLElement); // Returns a Bootstrap toast instance + toast._config.delay = duration; + + // show toast + toast.show(); + + //register event listener if toast is hidden + toastHTMLElement.addEventListener("hidden.bs.toast", () => { + // remove eventListener + toastHTMLElement.removeEventListener("hidden.bs.toast", this); + // remove toast + toastHTMLElement.remove(); + }); +} diff --git a/gui/src/js/settingsHandler.ts b/gui/src/js/settingsHandler.ts new file mode 100644 index 00000000..5b9d545e --- /dev/null +++ b/gui/src/js/settingsHandler.ts @@ -0,0 +1,152 @@ +import path from "node:path"; +import fs from "fs"; + +// pinia store setup +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); +// --------------------------------- + +console.log(process.env); +var appDataFolder = "undefined"; +if (typeof process.env["APPDATA"] !== "undefined") { + appDataFolder = process.env["APPDATA"]; + console.log(appDataFolder); +} else { + switch (process.platform) { + case "darwin": + appDataFolder = process.env["HOME"] + "/Library/Application Support"; + console.log(appDataFolder); + break; + case "linux": + appDataFolder = process.env["HOME"] + "/.config"; + console.log(appDataFolder); + break; + case "win32": + appDataFolder = "undefined"; + break; + default: + appDataFolder = "undefined"; + break; + } +} + +var configFolder = path.join(appDataFolder, "FreeDATA"); +var configPath = path.join(configFolder, "config.json"); + +console.log(appDataFolder); +console.log(configFolder); +console.log(configPath); + +// create config folder if not exists +if (!fs.existsSync(configFolder)) { + fs.mkdirSync(configFolder); +} + +// create config file if not exists with defaults +const configDefaultSettings = + '{\ + "modem_host": "127.0.0.1",\ + "modem_port": 3000,\ + "daemon_host": "127.0.0.1",\ + "daemon_port": 3001,\ + "rx_audio" : "",\ + "tx_audio" : "",\ + "mycall": "AA0AA-0",\ + "myssid": "0",\ + "mygrid": "JN40aa",\ + "radiocontrol" : "disabled",\ + "hamlib_deviceid": 6,\ + "hamlib_deviceport": "ignore",\ + "hamlib_stop_bits": "ignore",\ + "hamlib_data_bits": "ignore",\ + "hamlib_handshake": "ignore",\ + "hamlib_serialspeed": "ignore",\ + "hamlib_dtrstate": "ignore",\ + "hamlib_pttprotocol": "ignore",\ + "hamlib_ptt_port": "ignore",\ + "hamlib_dcd": "ignore",\ + "hamlbib_serialspeed_ptt": 9600,\ + "hamlib_rigctld_port" : 4532,\ + "hamlib_rigctld_ip" : "127.0.0.1",\ + "hamlib_rigctld_path" : "",\ + "hamlib_rigctld_server_port" : 4532,\ + "hamlib_rigctld_custom_args": "",\ + "tci_port" : 50001,\ + "tci_ip" : "127.0.0.1",\ + "spectrum": "waterfall",\ + "enable_scatter" : "False",\ + "enable_fft" : "False",\ + "enable_fsk" : "False",\ + "low_bandwidth_mode" : "False",\ + "theme" : "default",\ + "screen_height" : 430,\ + "screen_width" : 1050,\ + "update_channel" : "latest",\ + "beacon_interval" : 300,\ + "received_files_folder" : "None",\ + "tuning_range_fmin" : "-50.0",\ + "tuning_range_fmax" : "50.0",\ + "respond_to_cq" : "True",\ + "rx_buffer_size" : 16, \ + "enable_explorer" : "False", \ + "wftheme": 2, \ + "high_graphics" : "True",\ + "explorer_stats" : "False", \ + "auto_tune" : "False", \ + "enable_is_writing" : "True", \ + "shared_folder_path" : ".", \ + "enable_request_profile" : "True", \ + "enable_request_shared_folder" : "False", \ + "max_retry_attempts" : 5, \ + "enable_auto_retry" : "False", \ + "tx_delay" : 0, \ + "auto_start": 0, \ + "enable_sys_notification": 1, \ + "enable_mesh_features": "False" \ + }'; + +if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, configDefaultSettings); +} + +export function loadSettings() { + // load settings + var config = require(configPath); + + //config validation + // check running config against default config. + // if parameter not exists, add it to running config to prevent errors + console.log("CONFIG VALIDATION ----------------------------- "); + + var parsedConfig = JSON.parse(configDefaultSettings); + for (var key in parsedConfig) { + if (config.hasOwnProperty(key)) { + console.log("FOUND SETTTING [" + key + "]: " + config[key]); + } else { + console.log("MISSING SETTTING [" + key + "] : " + parsedConfig[key]); + config[key] = parsedConfig[key]; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + } + try { + if (key == "mycall") { + settings.mycall = config[key].split("-")[0]; + settings.myssid = config[key].split("-")[1]; + } else { + settings[key] = config[key]; + } + } catch (e) { + console.log(e); + } + } +} + +export function saveSettingsToFile() { + console.log("save settings to file..."); + let config = settings.getJSON(); + console.log(config); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); +} diff --git a/gui/src/js/sock.js b/gui/src/js/sock.js new file mode 100644 index 00000000..8ca2d0c7 --- /dev/null +++ b/gui/src/js/sock.js @@ -0,0 +1,870 @@ +var net = require("net"); +import { atob_FD, btoa_FD } from "./freedata"; +import { addDataToWaterfall } from "../js/waterfallHandler.js"; + +import { + newMessageReceived, + newBeaconReceived, + updateTransmissionStatus, + setStateSuccess, + setStateFailed, +} from "./chatHandler"; +import { displayToast } from "./popupHandler"; + +// ----------------- init pinia stores ------------- +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); +import { useStateStore } from "../store/stateStore.js"; +const stateStore = useStateStore(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + +var client = new net.Socket(); +var socketchunk = ""; // Current message, per connection. + +// split character +//const split_char = "\0;\1;"; +const split_char = "0;1;"; + +// global to keep track of Modem connection error emissions +var modemShowConnectStateError = 1; + +// network connection Timeout +setTimeout(connectModem, 2000); + +function connectModem() { + //exports.connectModem = function(){ + //console.log('connecting to Modem...') + + //clear message buffer after reconnecting or initial connection + socketchunk = ""; + + client.connect(settings.modem_port, settings.modem_host); +} + +client.on("connect", function () { + console.log("Modem connection established"); + + stateStore.busy_state = "-"; + stateStore.arq_state = "-"; + stateStore.frequency = "-"; + stateStore.mode = "-"; + stateStore.bandwidth = "-"; + stateStore.dbfs_level = 0; + stateStore.updateTncState(client.readyState); + + modemShowConnectStateError = 1; +}); + +client.on("error", function (err) { + if (modemShowConnectStateError == 1) { + console.log("Modem connection error"); + console.log(err); + modemShowConnectStateError = 0; + } + setTimeout(connectModem, 500); + client.destroy(); + stateStore.busy_state = "-"; + stateStore.arq_state = "-"; + stateStore.frequency = "-"; + stateStore.mode = "-"; + stateStore.bandwidth = "-"; + stateStore.dbfs_level = 0; + stateStore.updateTncState(client.readyState); +}); + +/* +client.on('close', function(data) { + console.log(' Modem connection closed'); + setTimeout(connectModem, 2000) +}); +*/ + +client.on("end", function (data) { + console.log("Modem connection ended"); + console.log(data); + stateStore.busy_state = "-"; + stateStore.arq_state = "-"; + stateStore.frequency = "-"; + stateStore.mode = "-"; + stateStore.bandwidth = "-"; + stateStore.dbfs_level = 0; + stateStore.updateTncState(client.readyState); + client.destroy(); + + setTimeout(connectModem, 500); +}); + +function writeTncCommand(command) { + console.log(command); + // we use the writingCommand function to update our TCPIP state because we are calling this function a lot + // if socket opened, we are able to run commands + + if (client.readyState == "open") { + client.write(command + "\n"); + } + + if (client.readyState == "closed") { + console.log("Modem SOCKET CONNECTION CLOSED!"); + } + + if (client.readyState == "opening") { + console.log("connecting to Modem..."); + } +} + +client.on("data", function (socketdata) { + stateStore.updateTncState(client.readyState); + + /* + inspired by: + stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in + */ + + socketdata = socketdata.toString("utf8"); // convert data to string + socketchunk += socketdata; // append data to buffer so we can stick long data together + + // check if we received begin and end of json data + if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) { + var data = ""; + + // split data into chunks if we received multiple commands + socketchunk = socketchunk.split("\n"); + //don't think this is needed anymore + //data = JSON.parse(socketchunk[0]) + + // search for empty entries in socketchunk and remove them + for (let i = 0; i < socketchunk.length; i++) { + if (socketchunk[i] === "") { + socketchunk.splice(i, 1); + } + } + + //iterate through socketchunks array to execute multiple commands in row + for (let i = 0; i < socketchunk.length; i++) { + //check if data is not empty + if (socketchunk[i].length > 0) { + //try to parse JSON + try { + data = JSON.parse(socketchunk[i]); + } catch (e) { + console.log("Throwing away data!!!!\n" + e); // "SyntaxError + //console.log(e); // "SyntaxError + console.log(socketchunk[i]); + socketchunk = ""; + //If we're here, I don't think we want to process any data that may be in data variable + continue; + } + } + //console.log(data) + if (data["command"] == "modem_state") { + //console.log(data) + + stateStore.rx_buffer_length = data["rx_buffer_length"]; + stateStore.frequency = data["frequency"]; + stateStore.busy_state = data["modem_state"]; + stateStore.arq_state = data["arq_state"]; + stateStore.mode = data["mode"]; + stateStore.bandwidth = data["bandwidth"]; + stateStore.dbfs_level = data["audio_dbfs"]; + stateStore.ptt_state = data["ptt_state"]; + stateStore.speed_level = data["speed_level"]; + stateStore.fft = JSON.parse(data["fft"]); + stateStore.channel_busy = data["channel_busy"]; + stateStore.channel_busy_slot = data["channel_busy_slot"]; + + addDataToWaterfall(JSON.parse(data["fft"])); + + if (data["scatter"].length > 0) { + stateStore.scatter = data["scatter"]; + } + // s meter strength + stateStore.s_meter_strength_raw = data["strength"]; + if (stateStore.s_meter_strength_raw == "") { + stateStore.s_meter_strength_raw = "Unsupported"; + stateStore.s_meter_strength_percent = 0; + } else { + // https://www.moellerstudios.org/converting-amplitude-representations/ + stateStore.s_meter_strength_percent = Math.round( + Math.pow(10, stateStore.s_meter_strength_raw / 20) * 100, + ); + } + + stateStore.dbfs_level_percent = Math.round( + Math.pow(10, stateStore.dbfs_level / 20) * 100, + ); + stateStore.dbfs_level = Math.round(stateStore.dbfs_level); + + stateStore.arq_total_bytes = data["total_bytes"]; + stateStore.heard_stations = data["stations"]; + stateStore.dxcallsign = data["dxcallsign"]; + + stateStore.beacon_state = data["beacon_state"]; + stateStore.audio_recording = data["audio_recording"]; + + stateStore.hamlib_status = data["hamlib_status"]; + stateStore.alc = data["alc"]; + stateStore.rf_level = data["rf_level"]; + + stateStore.is_codec2_traffic = data["is_codec2_traffic"]; + + stateStore.arq_session_state = data["arq_session"]; + stateStore.arq_state = data["arq_state"]; + stateStore.arq_transmission_percent = data["arq_transmission_percent"]; + stateStore.arq_seconds_until_finish = data["arq_seconds_until_finish"]; + stateStore.arq_seconds_until_timeout = + data["arq_seconds_until_timeout"]; + stateStore.arq_seconds_until_timeout_percent = + (stateStore.arq_seconds_until_timeout / 180) * 100; + + if (data["speed_list"].length > 0) { + prepareStatsDataForStore(data["speed_list"]); + } + + // TODO: Remove ported objects + /* + let Data = { + mycallsign: data["mycallsign"], + mygrid: data["mygrid"], + //channel_state: data['CHANNEL_STATE'], + + info: data["info"], + rx_msg_buffer_length: data["rx_msg_buffer_length"], + tx_n_max_retries: data["tx_n_max_retries"], + arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"], + arq_tx_n_bursts: data["arq_tx_n_bursts"], + arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"], + arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"], + arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"], + arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"], + arq_n_arq_frames_per_data_frame: + data["arq_n_arq_frames_per_data_frame"], + arq_bytes_per_minute: data["arq_bytes_per_minute"], + arq_compression_factor: data["arq_compression_factor"], + routing_table: data["routing_table"], + mesh_signalling_table: data["mesh_signalling_table"], + listen: data["listen"], + //speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}], + }; + */ + //continue to next for loop iteration, nothing else needs to be done here + continue; + } + + // ----------- catch modem messages START ----------- + //init message variable + var message = ""; + if (data["freedata"] == "modem-message") { + // break early if we received a dummy callsign + // thats a kind of hotfix, as long as the modem isnt handling this better + if ( + data["dxcallsign"] == "AA0AA-0" || + data["dxcallsign"] == "ZZ9YY-0" + ) { + break; + } + + console.log(data); + + switch (data["fec"]) { + case "is_writing": + // RX'd FECiswriting + break; + + case "broadcast": + // RX'd FEC BROADCAST + var encoded_data = atob_FD(data["data"]); + var splitted_data = encoded_data.split(split_char); + var messageArray = []; + if (splitted_data[0] == "m") { + messageArray.push(data); + console.log(data); + } + break; + } + + switch (data["cq"]) { + case "transmitting": + // CQ TRANSMITTING + displayToast( + "success", + "bi-arrow-left-right", + "Transmitting CQ", + 5000, + ); + break; + + case "received": + // CQ RECEIVED + message = "CQ from " + data["dxcallsign"]; + displayToast("success", "bi-person-arms-up", message, 5000); + break; + } + + switch (data["qrv"]) { + case "transmitting": + // QRV TRANSMITTING + displayToast( + "info", + "bi-person-raised-hand", + "Transmitting QRV ", + 5000, + ); + break; + + case "received": + // QRV RECEIVED + message = "QRV from " + data["dxcallsign"] + " | " + data["dxgrid"]; + displayToast("success", "bi-person-raised-hand", message, 5000); + break; + } + + switch (data["beacon"]) { + case "transmitting": + // BEACON TRANSMITTING + displayToast( + "success", + "bi-broadcast-pin", + "Transmitting beacon", + 5000, + ); + break; + + case "received": + // BEACON RECEIVED + newBeaconReceived(data); + + message = + "Beacon from " + data["dxcallsign"] + " | " + data["dxgrid"]; + displayToast("info", "bi-broadcast", message, 5000); + break; + } + + switch (data["ping"]) { + case "transmitting": + // PING TRANSMITTING + message = "Sending ping to " + data["dxcallsign"]; + displayToast("success", "bi-arrow-right", message, 5000); + break; + + case "received": + // PING RECEIVED + message = + "Ping request from " + + data["dxcallsign"] + + " | " + + data["dxgrid"]; + displayToast("success", "bi-arrow-right-short", message, 5000); + break; + + case "acknowledge": + // PING ACKNOWLEDGE + message = + "Received ping-ack from " + + data["dxcallsign"] + + " | " + + data["dxgrid"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + } + + // ARQ SESSION && freedata == modem-message + if (data["arq"] == "session") { + switch (data["status"]) { + case "connecting": + // ARQ Open + break; + + case "connected": + // ARQ Opening + break; + + case "waiting": + // ARQ Opening + break; + + case "close": + // ARQ Closing + break; + + case "failed": + // ARQ Failed + break; + } + } + // ARQ TRANSMISSION && freedata == modem-message + if (data["arq"] == "transmission") { + switch (data["status"]) { + case "opened": + // ARQ Open + message = "ARQ session opened: " + data["dxcallsign"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + + case "opening": + // ARQ Opening IRS/ISS + if (data["irs"] == "False") { + message = "ARQ session opening: " + data["dxcallsign"]; + displayToast("info", "bi-arrow-left-right", message, 5000); + break; + } else { + message = "ARQ sesson request from: " + data["dxcallsign"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + } + + case "waiting": + // ARQ waiting + message = "Channel busy | ARQ protocol is waiting"; + displayToast("warning", "bi-hourglass-split", message, 5000); + break; + + case "receiving": + // ARQ RX + break; + + case "failed": + // ARQ TX Failed + if (data["reason"] == "protocol version missmatch") { + message = "Protocol version mismatch!"; + displayToast("danger", "bi-chevron-bar-expand", message, 5000); + setStateFailed(); + + break; + } else { + message = "Transmission failed"; + displayToast("danger", "bi-x-octagon", message, 5000); + updateTransmissionStatus(data); + setStateFailed(); + break; + } + switch (data["irs"]) { + case "True": + updateTransmissionStatus(data); + + break; + default: + updateTransmissionStatus(data); + break; + } + break; + + case "received": + // ARQ data received + + console.log(data); + // we need to encode here to do a deep check for checking if file or message + //var encoded_data = atob(data['data']) + var encoded_data = atob_FD(data["data"]); + var splitted_data = encoded_data.split(split_char); + + // new message received + if (splitted_data[0] == "m") { + console.log(splitted_data); + newMessageReceived(splitted_data, data); + } + + break; + + case "transmitting": + // ARQ transmitting + updateTransmissionStatus(data); + break; + + case "transmitted": + // ARQ transmitted + message = "Data transmitted"; + displayToast("success", "bi-check-sqaure", message, 5000); + updateTransmissionStatus(data); + setStateSuccess(); + + break; + } + } + } + } + + //finally delete message buffer + socketchunk = ""; + } +}); + +// Send Ping +//exports.sendPing = function (dxcallsign) { +export function sendPing(dxcallsign) { + var command = + '{"type" : "ping", "command" : "ping", "dxcallsign" : "' + + dxcallsign + + '"}'; + writeTncCommand(command); +} + +// Send Mesh Ping +//exports.sendMeshPing = function (dxcallsign) { +function sendMeshPing(dxcallsign) { + var command = + '{"type" : "mesh", "command" : "ping", "dxcallsign" : "' + + dxcallsign + + '"}'; + writeTncCommand(command); +} + +// Send CQ +//exports.sendCQ = function () { +export function sendCQ() { + var command = '{"type" : "broadcast", "command" : "cqcqcq"}'; + writeTncCommand(command); +} + +// Set AUDIO Level +export function setTxAudioLevel(value) { + var command = + '{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}'; + writeTncCommand(command); +} + +// Send Message +export function sendMessage(obj) { + let dxcallsign = obj.dxcallsign; + let checksum = obj.checksum; + let uuid = obj.uuid; + let command = obj.command; + + let filename = Object.keys(obj._attachments)[0]; + //let filetype = filename.split(".")[1] + let filetype = obj._attachments[filename].content_type; + let file = obj._attachments[filename].data; + + //console.log(obj._attachments) + //console.log(filename) + //console.log(filetype) + //console.log(file) + + let data_with_attachment = + obj.timestamp + + split_char + + obj.msg + + split_char + + filename + + split_char + + filetype + + split_char + + file; + + let data = btoa_FD( + "m" + + split_char + + command + + split_char + + checksum + + split_char + + uuid + + split_char + + data_with_attachment, + ); + + // TODO: REMOVE mode and frames from Modem! + var mode = 255; + var frames = 5; + + command = + '{"type" : "arq", "command" : "send_raw", "uuid" : "' + + uuid + + '", "parameter" : [{"dxcallsign" : "' + + dxcallsign + + '", "mode" : "' + + mode + + '", "n_frames" : "' + + frames + + '", "data" : "' + + data + + '", "attempts": "10"}]}'; + console.log(command); + writeTncCommand(command); +} +/* +// Send Request message +//It would be then „m + split + request + split + request-type“ +function sendRequest(dxcallsign, mode, frames, data, command) { + data = btoa_FD("m" + split_char + command + split_char + data); + command = + '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + + dxcallsign + + '", "mode" : "' + + mode + + '", "n_frames" : "' + + frames + + '", "data" : "' + + data + + '", "attempts": "10"}]}'; + console.log(command); + console.log("--------------REQ--------------------"); + writeTncCommand(command); +} + +// Send Response message +//It would be then „m + split + request + split + request-type“ +function sendResponse(dxcallsign, mode, frames, data, command) { + data = btoa_FD("m" + split_char + command + split_char + data); + command = + '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + + dxcallsign + + '", "mode" : "' + + mode + + '", "n_frames" : "' + + frames + + '", "data" : "' + + data + + '", "attempts": "10"}]}'; + console.log(command); + console.log("--------------RES--------------------"); + writeTncCommand(command); +} + +//Send station info request +//exports.sendRequestInfo = function (dxcallsign) { +function sendRequestInfo(dxcallsign) { + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendRequest(dxcallsign, 255, 1, "0", "req"); +} + +//Send shared folder file list request +//exports.sendRequestSharedFolderList = function (dxcallsign) { +function sendRequestSharedFolderList(dxcallsign) { + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendRequest(dxcallsign, 255, 1, "1", "req"); +} + +//Send shared file request +//exports.sendRequestSharedFile = function (dxcallsign, file) { +function sendRequestSharedFile(dxcallsign, file) { + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendRequest(dxcallsign, 255, 1, "2" + file, "req"); +} + +//Send station info response +//exports.sendResponseInfo = function (dxcallsign, userinfo) { +function sendResponseInfo(dxcallsign, userinfo) { + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendResponse(dxcallsign, 255, 1, userinfo, "res-0"); +} + +//Send shared folder response +//exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) { +function sendResponseSharedFolderList(dxcallsign, sharedFolderList) { + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1"); +} + +//Send shared file response +//exports.sendResponseSharedFile = function ( +function sendResponseSharedFile(dxcallsign, sharedFile, sharedFileData) { + console.log( + "In sendResponseSharedFile", + dxcallsign, + sharedFile, + sharedFileData, + ); + //Command 0 = user/station information + //Command 1 = shared folder list + //Command 2 = shared file transfer + sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2"); +} +*/ +//STOP TRANSMISSION +export function stopTransmission() { + var command = '{"type" : "arq", "command": "stop_transmission"}'; + writeTncCommand(command); +} + +// Get RX BUffer +export function getRxBuffer() { + var command = '{"type" : "get", "command" : "rx_buffer"}'; + + writeTncCommand(command); +} + +// START BEACON +export function startBeacon(interval) { + var command = + '{"type" : "broadcast", "command" : "start_beacon", "parameter": "' + + interval + + '"}'; + writeTncCommand(command); +} + +// STOP BEACON +export function stopBeacon() { + var command = '{"type" : "broadcast", "command" : "stop_beacon"}'; + writeTncCommand(command); +} + +// OPEN ARQ SESSION +export function connectARQ(dxcallsign) { + var command = + '{"type" : "arq", "command" : "connect", "dxcallsign": "' + + dxcallsign + + '", "attempts": "10"}'; + writeTncCommand(command); +} + +// CLOSE ARQ SESSION +export function disconnectARQ() { + var command = '{"type" : "arq", "command" : "disconnect"}'; + writeTncCommand(command); +} + +// SEND TEST FRAME +export function sendTestFrame() { + var command = '{"type" : "set", "command" : "send_test_frame"}'; + writeTncCommand(command); +} + +// SEND FEC +export function sendFEC(mode, payload) { + var command = + '{"type" : "fec", "command" : "transmit", "mode" : "' + + mode + + '", "payload" : "' + + payload + + '"}'; + writeTncCommand(command); +} + +// SEND FEC IS WRITING +export function sendFecIsWriting(mycallsign) { + var command = + '{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' + + mycallsign + + '"}'; + writeTncCommand(command); +} + +// SEND FEC TO BROADCASTCHANNEL +//export function sendBroadcastChannel(channel, data_out, uuid) { +export function sendBroadcastChannel(obj) { + //let checksum = obj.checksum; + let command = obj.command; + let uuid = obj.uuid; + let channel = obj.dxcallsign; + let data_out = obj.msg; + + let data = btoa_FD( + "m" + + split_char + + channel + + //split_char + + //checksum + + split_char + + uuid + + split_char + + data_out, + ); + console.log(data.length); + let payload = data; + command = + '{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' + + payload + + '"}'; + writeTncCommand(command); +} + +// RECORD AUDIO +export function record_audio() { + var command = '{"type" : "set", "command" : "record_audio"}'; + writeTncCommand(command); +} + +// SET FREQUENCY +export function set_frequency(frequency) { + var command = + '{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}"; + writeTncCommand(command); +} + +// SET MODE +export function set_mode(mode) { + var command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}'; + writeTncCommand(command); +} + +// SET rf_level +export function set_rf_level(rf_level) { + var command = + '{"type" : "set", "command" : "rf_level", "rf_level": "' + rf_level + '"}'; + writeTncCommand(command); +} + +// https://stackoverflow.com/a/50579690 +// crc32 calculation +//console.log(crc32('abc')); +//console.log(crc32('abc').toString(16).toUpperCase()); // hex +/* +function crc32(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; +}; +*/ + +// TODO Maybe moving this to another module +function prepareStatsDataForStore(data) { + // dummy data + //state.arq_speed_list = [{"snr":0.0,"bpm":104,"timestamp":1696189769},{"snr":0.0,"bpm":80,"timestamp":1696189778},{"snr":0.0,"bpm":70,"timestamp":1696189783},{"snr":0.0,"bpm":58,"timestamp":1696189792},{"snr":0.0,"bpm":52,"timestamp":1696189797},{"snr":"NaN","bpm":42,"timestamp":1696189811},{"snr":0.0,"bpm":22,"timestamp":1696189875},{"snr":0.0,"bpm":21,"timestamp":1696189881},{"snr":0.0,"bpm":17,"timestamp":1696189913},{"snr":0.0,"bpm":15,"timestamp":1696189932},{"snr":0.0,"bpm":15,"timestamp":1696189937},{"snr":0.0,"bpm":14,"timestamp":1696189946},{"snr":-6.1,"bpm":14,"timestamp":1696189954},{"snr":-6.1,"bpm":14,"timestamp":1696189955},{"snr":-5.5,"bpm":28,"timestamp":1696189963},{"snr":-5.5,"bpm":27,"timestamp":1696189963}] + + var speed_listSize = 0; + if (typeof data == "undefined") { + speed_listSize = 0; + } else { + speed_listSize = data.length; + } + + var speed_list_bpm = []; + + for (let i = 0; i < speed_listSize; i++) { + speed_list_bpm.push(data[i].bpm); + } + + var speed_list_timestamp = []; + + for (let i = 0; i < speed_listSize; i++) { + let timestamp = data[i].timestamp * 1000; + let h = new Date(timestamp).getHours(); + let m = new Date(timestamp).getMinutes(); + let s = new Date(timestamp).getSeconds(); + let time = h + ":" + m + ":" + s; + speed_list_timestamp.push(time); + } + + var speed_list_snr = []; + for (let i = 0; i < speed_listSize; i++) { + let snr = NaN; + if (data[i].snr !== 0) { + snr = data[i].snr; + } else { + snr = NaN; + } + speed_list_snr.push(snr); + } + + stateStore.arq_speed_list_bpm = speed_list_bpm; + stateStore.arq_speed_list_timestamp = speed_list_timestamp; + stateStore.arq_speed_list_snr = speed_list_snr; +} diff --git a/gui/src/js/waterfallHandler.js b/gui/src/js/waterfallHandler.js new file mode 100644 index 00000000..7d4aa15c --- /dev/null +++ b/gui/src/js/waterfallHandler.js @@ -0,0 +1,29 @@ +import { Spectrum } from "../assets/waterfall/spectrum.js"; + +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + +var spectrum = new Object(); + +export function initWaterfall() { + spectrum = new Spectrum("waterfall", { + spectrumPercent: 0, + wf_rows: 192, //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container + }); + + console.log(settings.wftheme); + spectrum.setColorMap(settings.wftheme); +} + +export function addDataToWaterfall(data) { + //console.log(spectrum) + try { + spectrum.addData(data); + } catch (e) { + //console.log(e); + } +} diff --git a/gui/src/log-module.html b/gui/src/log-module.html deleted file mode 100644 index d7ec5d02..00000000 --- a/gui/src/log-module.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - FreeDATA - Live Log - - - - - - - - -
-
- - - - - - - - - - - - -
TimestampTypeAreaLog entry
-
-
- - diff --git a/gui/src/main.ts b/gui/src/main.ts new file mode 100644 index 00000000..2dae75b8 --- /dev/null +++ b/gui/src/main.ts @@ -0,0 +1,29 @@ +import { createApp } from "vue"; +import { createPinia } from "pinia"; +import { loadSettings } from "./js/settingsHandler"; + +import "./styles.css"; + +// Import all of Bootstrap's JS +//import * as bootstrap from 'bootstrap' + +import "bootstrap/dist/js/bootstrap.bundle.min.js"; +import "bootstrap/dist/css/bootstrap.css"; +import "bootstrap-icons/font/bootstrap-icons.css"; + +// Import our custom CSS +//import './scss/styles.scss' + +import App from "./App.vue"; +const app = createApp(App); +//.mount('#app').$nextTick(() => postMessage({ payload: 'removeLoading' }, '*')) +const pinia = createPinia(); +app.mount("#app"); + +app.use(pinia); +loadSettings(); + +//import './js/settingsHandler.js' +import "./js/daemon"; +import "./js/sock.js"; +//import './js/settingsHandler.js' diff --git a/gui/src/mesh-module.html b/gui/src/mesh-module.html deleted file mode 100644 index 396ad53e..00000000 --- a/gui/src/mesh-module.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - FreeDATA - Mesh Table - - - - - - - - - - diff --git a/gui/src/scss/styles.scss b/gui/src/scss/styles.scss new file mode 100644 index 00000000..ea4b3833 --- /dev/null +++ b/gui/src/scss/styles.scss @@ -0,0 +1,2 @@ +// Import all of Bootstrap's CSS +@import "bootstrap/scss/bootstrap"; \ No newline at end of file diff --git a/gui/src/splash.html b/gui/src/splash.html deleted file mode 100644 index fa3735af..00000000 --- a/gui/src/splash.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/gui/src/store/audioStore.js b/gui/src/store/audioStore.js new file mode 100644 index 00000000..cf7282cc --- /dev/null +++ b/gui/src/store/audioStore.js @@ -0,0 +1,55 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + +export const useAudioStore = defineStore("audioStore", () => { + var inputDevices = ref([{ id: 0, name: "no input devices" }]); + var outputDevices = ref([{ id: 0, name: "no output devices" }]); + + var startupInputDevice = ref(0); + var startupOutputDevice = ref(0); + + function getInputDevices() { + var html = ""; + for (var key in inputDevices.value) { + let selected = ""; + if (inputDevices.value[key]["name"] == settings.rx_audio) { + selected = "selected"; + } else { + selected = ""; + } + + html += ``; + } + return html; + } + + function getOutputDevices() { + var html = ""; + for (var key in outputDevices.value) { + let selected = ""; + if (outputDevices.value[key]["name"] == settings.tx_audio) { + selected = "selected"; + } else { + selected = ""; + } + html += ``; + } + return html; + } + + return { + inputDevices, + outputDevices, + getInputDevices, + getOutputDevices, + startupInputDevice, + startupOutputDevice, + }; +}); diff --git a/gui/src/store/chatStore.js b/gui/src/store/chatStore.js new file mode 100644 index 00000000..327f0685 --- /dev/null +++ b/gui/src/store/chatStore.js @@ -0,0 +1,92 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +export const useChatStore = defineStore("chatStore", () => { + var chat_filter = ref([ + { type: "newchat" }, + { type: "received" }, + { type: "transmit" }, + { type: "ping-ack" }, + { type: "broadcast_received" }, + { type: "broadcast_transmit" }, + //{ type: "request" }, + //{ type: "response" }, + ]); + + var selectedCallsign = ref(); + // we need a default value in our ref because of our message info modal + var selectedMessageObject = ref({ + command: "msg", + hmac_signed: false, + percent: 0, + is_new: false, + _id: "2ead6698", + timestamp: 1697289795, + dxcallsign: "DJ2LS-0", + dxgrid: "null", + msg: "test", + checksum: "", + type: "transmit", + status: "transmitting", + attempt: 1, + uuid: "2ead6698", + duration: 0, + nacks: 0, + speed_list: "null", + _attachments: { + "": { + content_type: "text", + data: "", + }, + }, + }); + var inputText = ref(""); + var inputFile = ref(); + var inputFileName = ref(); + var inputFileType = ref(); + var inputFileSize = ref(); + + var callsign_list = ref(); + var sorted_chat_list = ref(); + var unsorted_chat_list = ref([]); + + var sorted_beacon_list = ref({}); + var unsorted_beacon_list = ref({}); + + var chartSpeedPER0 = ref(); + var chartSpeedPER25 = ref(); + var chartSpeedPER75 = ref(); + + // var beaconDataArray = ref([-3, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5, 10, 8, 5, 3, 0, -5]) + // var beaconLabelArray = ref(['18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '18:10', '19:00', '23:00', '01:13', '04:25', '08:15', '09:12', '01:13', '04:25', '08:15', '09:12']) + var beaconDataArray = ref([]); + var beaconLabelArray = ref([]); + + var arq_speed_list_bpm = ref([]); + var arq_speed_list_timestamp = ref([]); + var arq_speed_list_snr = ref([]); + + return { + selectedCallsign, + selectedMessageObject, + inputText, + chat_filter, + callsign_list, + sorted_chat_list, + unsorted_chat_list, + inputFileName, + inputFileSize, + inputFileType, + inputFile, + chartSpeedPER0, + chartSpeedPER25, + chartSpeedPER75, + beaconDataArray, + beaconLabelArray, + unsorted_beacon_list, + sorted_beacon_list, + arq_speed_list_bpm, + arq_speed_list_snr, + arq_speed_list_timestamp, + }; +}); diff --git a/gui/src/store/index.js b/gui/src/store/index.js new file mode 100644 index 00000000..b0b38085 --- /dev/null +++ b/gui/src/store/index.js @@ -0,0 +1,5 @@ +import { createPinia } from "pinia"; + +const pinia = createPinia(); + +export default pinia; diff --git a/gui/src/store/settingsStore.js b/gui/src/store/settingsStore.js new file mode 100644 index 00000000..00787ff2 --- /dev/null +++ b/gui/src/store/settingsStore.js @@ -0,0 +1,228 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +export const useSettingsStore = defineStore("settingsStore", () => { + // audio + var tx_audio = ref(); + var rx_audio = ref(); + + // network + var modem_host = ref("127.0.0.1"); + var modem_port = ref(3000); + var daemon_host = ref(modem_host.value); + var daemon_port = ref(modem_port.value + 1); + + // app + var screen_height = ref(430); + var screen_width = ref(1050); + var theme = ref("default"); + var wftheme = ref(2); + var high_graphics = ref("False"); + var auto_start = ref(0); + var enable_sys_notification = ref(1); + + // chat + var shared_folder_path = ref("."); + var enable_request_profile = ref("True"); + var enable_request_shared_folder = ref("False"); + var max_retry_attempts = ref(5); + var enable_auto_retry = ref("False"); + + // station + var mycall = ref("AA0AA-5"); + var myssid = ref(0); + var mygrid = ref("JN20aa"); + + // rigctld + var hamlib_rigctld_port = ref(4532); + var hamlib_rigctld_ip = ref("127.0.0.1"); + var radiocontrol = ref("disabled"); + var hamlib_deviceid = ref("RIG_MODEL_DUMMY_NOVFO"); + var hamlib_deviceport = ref("ignore"); + var hamlib_stop_bits = ref("ignore"); + var hamlib_data_bits = ref("ignore"); + var hamlib_handshake = ref("ignore"); + var hamlib_serialspeed = ref("ignore"); + var hamlib_dtrstate = ref("ignore"); + var hamlib_pttprotocol = ref("ignore"); + var hamlib_ptt_port = ref("ignore"); + var hamlib_dcd = ref("ignore"); + var hamlbib_serialspeed_ptt = ref(9600); + var hamlib_rigctld_path = ref(""); + var hamlib_rigctld_server_port = ref(4532); + var hamlib_rigctld_custom_args = ref(""); + + // tci + var tci_ip = ref("127.0.0.1"); + var tci_port = ref(50001); + + //modem + var spectrum = ref("waterfall"); + var enable_scatter = ref("False"); + var enable_fft = ref("False"); + var enable_fsk = ref("False"); + var low_bandwidth_mode = ref("False"); + var update_channel = ref("latest"); + var beacon_interval = ref(300); + var received_files_folder = ref("None"); + var tuning_range_fmin = ref(-50.0); + var tuning_range_fmax = ref(50.0); + var respond_to_cq = ref("True"); + var rx_buffer_size = ref(16); + var enable_explorer = ref("False"); + var explorer_stats = ref("False"); + var auto_tune = ref("False"); + var enable_is_writing = ref("True"); + var tx_delay = ref(0); + var enable_mesh_features = ref("False"); + var serial_devices = ref(); + + function getSerialDevices() { + if (this.hamlib_deviceport == "ignore") + var html = + ''; + else + var html = + ''; + for (var key in serial_devices.value) { + let selected = ""; + if (serial_devices.value[key]["port"] == this.hamlib_deviceport) { + selected = "selected"; + } else { + selected = ""; + } + + html += ``; + } + return html; + } + + function getJSON() { + var config_export = { + modem_host: modem_host.value, + modem_port: modem_port.value, + daemon_host: modem_host.value, + daemon_port: (parseInt(modem_port.value) + 1).toString(), + mycall: mycall.value, + myssid: myssid.value, + mygrid: mygrid.value, + radiocontrol: radiocontrol.value, + hamlib_deviceid: hamlib_deviceid.value, + hamlib_deviceport: hamlib_deviceport.value, + hamlib_stop_bits: hamlib_stop_bits.value, + hamlib_data_bits: hamlib_data_bits.value, + hamlib_handshake: hamlib_handshake.value, + hamlib_serialspeed: hamlib_serialspeed.value, + hamlib_dtrstate: hamlib_dtrstate.value, + hamlib_pttprotocol: hamlib_pttprotocol.value, + hamlib_ptt_port: hamlib_ptt_port.value, + hamlib_dcd: hamlib_dcd.value, + hamlbib_serialspeed_ptt: hamlib_serialspeed.value, + hamlib_rigctld_port: hamlib_rigctld_port.value, + hamlib_rigctld_ip: hamlib_rigctld_ip.value, + hamlib_rigctld_path: hamlib_rigctld_path.value, + hamlib_rigctld_server_port: hamlib_rigctld_server_port.value, + hamlib_rigctld_custom_args: hamlib_rigctld_custom_args.value, + tci_port: tci_port.value, + tci_ip: tci_ip.value, + spectrum: spectrum.value, + enable_scatter: enable_scatter.value, + enable_fft: enable_fft.value, + enable_fsk: enable_fsk.value, + low_bandwidth_mode: low_bandwidth_mode.value, + theme: theme.value, + screen_height: screen_height.value, + screen_width: screen_width.value, + update_channel: update_channel.value, + beacon_interval: beacon_interval.value, + received_files_folder: received_files_folder.value, + tuning_range_fmin: tuning_range_fmin.value, + tuning_range_fmax: tuning_range_fmax.value, + respond_to_cq: respond_to_cq.value, + rx_buffer_size: rx_buffer_size.value, + enable_explorer: enable_explorer.value, + wftheme: wftheme.value, + high_graphics: high_graphics.value, + explorer_stats: explorer_stats.value, + auto_tune: auto_tune.value, + enable_is_writing: enable_is_writing.value, + shared_folder_path: shared_folder_path.value, + enable_request_profile: enable_request_profile.value, + enable_request_shared_folder: enable_request_shared_folder.value, + max_retry_attempts: max_retry_attempts.value, + enable_auto_retry: enable_auto_retry.value, + tx_delay: tx_delay.value, + auto_start: auto_start.value, + enable_sys_notification: enable_sys_notification.value, + enable_mesh_features: enable_mesh_features.value, + tx_audio: tx_audio.value, + rx_audio: rx_audio.value, + }; + + return config_export; + } + + return { + modem_host, + modem_port, + daemon_host, + daemon_port, + screen_height, + screen_width, + theme, + wftheme, + high_graphics, + auto_start, + enable_sys_notification, + shared_folder_path, + enable_request_profile, + enable_request_shared_folder, + max_retry_attempts, + enable_auto_retry, + mycall, + myssid, + mygrid, + hamlib_rigctld_port, + hamlib_rigctld_ip, + radiocontrol, + hamlib_deviceid, + hamlib_deviceport, + hamlib_stop_bits, + hamlib_data_bits, + hamlib_handshake, + hamlib_serialspeed, + hamlib_dtrstate, + hamlib_pttprotocol, + hamlib_ptt_port, + hamlib_dcd, + hamlbib_serialspeed_ptt, + hamlib_rigctld_path, + hamlib_rigctld_server_port, + hamlib_rigctld_custom_args, + tci_ip, + tci_port, + spectrum, + enable_scatter, + enable_fft, + enable_fsk, + low_bandwidth_mode, + update_channel, + beacon_interval, + received_files_folder, + tuning_range_fmin, + tuning_range_fmax, + respond_to_cq, + rx_buffer_size, + enable_explorer, + explorer_stats, + auto_tune, + enable_is_writing, + tx_delay, + enable_mesh_features, + getJSON, + tx_audio, + rx_audio, + getSerialDevices, + serial_devices, + }; +}); diff --git a/gui/src/store/stateStore.js b/gui/src/store/stateStore.js new file mode 100644 index 00000000..8d7041e8 --- /dev/null +++ b/gui/src/store/stateStore.js @@ -0,0 +1,183 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; +import * as bootstrap from "bootstrap"; + +export const useStateStore = defineStore("stateStore", () => { + var busy_state = ref("-"); + var arq_state = ref("-"); + var frequency = ref("-"); + var new_frequency = ref(0); + var mode = ref("-"); + var rf_level = ref("10"); + var bandwidth = ref("-"); + var dbfs_level_percent = ref(0); + var dbfs_level = ref(0); + + var ptt_state = ref("False"); + + var speed_level = ref(0); + var fft = ref(); + var channel_busy = ref(""); + var channel_busy_slot = ref(); + var scatter = ref(); + var s_meter_strength_percent = ref(0); + var s_meter_strength_raw = ref(0); + + var modem_connection = ref("disconnected"); + var modemStartCount = ref(0); + var modem_running_state = ref("--------"); + + var arq_total_bytes = ref(0); + var arq_transmission_percent = ref(0); + + var heard_stations = ref(""); + var dxcallsign = ref(""); + + var arq_session_state = ref(""); + var arq_state = ref(""); + var beacon_state = ref("False"); + + var audio_recording = ref(""); + + var hamlib_status = ref(""); + var audio_level = ref(""); + var alc = ref(""); + + var is_codec2_traffic = ref(""); + + var arq_speed_list_timestamp = ref([]); + var arq_speed_list_bpm = ref([]); + var arq_speed_list_snr = ref([]); + + var arq_seconds_until_finish = ref(); + var arq_seconds_until_timeout = ref(); + var arq_seconds_until_timeout_percent = ref(); + + var rigctld_started = ref(); + var rigctld_process = ref(); + + var python_version = ref(); + var modem_version = ref(); + + var rx_buffer_length = ref(); + + function getChannelBusySlotState(slot) { + const slot_state = channel_busy_slot.value; + + if (typeof slot_state !== "undefined") { + // Replace 'False' with 'false' to match JavaScript's boolean representation + const string = slot_state + .replace(/False/g, "false") + .replace(/True/g, "true"); + + // Parse the string to get an array + const arr = JSON.parse(string); + + return arr[slot]; + } else { + // Handle the undefined case + return false; + } + } + + function updateTncState(state) { + modem_connection.value = state; + + if (modem_connection.value == "open") { + // collapse settings screen + var collapseFirstRow = new bootstrap.Collapse( + document.getElementById("collapseFirstRow"), + { toggle: false }, + ); + collapseFirstRow.hide(); + var collapseSecondRow = new bootstrap.Collapse( + document.getElementById("collapseSecondRow"), + { toggle: false }, + ); + collapseSecondRow.hide(); + var collapseThirdRow = new bootstrap.Collapse( + document.getElementById("collapseThirdRow"), + { toggle: false }, + ); + collapseThirdRow.show(); + var collapseFourthRow = new bootstrap.Collapse( + document.getElementById("collapseFourthRow"), + { toggle: false }, + ); + collapseFourthRow.show(); + + //Set tuning for fancy graphics mode (high/low CPU) + //set_CPU_mode(); + + //GUI will auto connect to TNC if already running, if that is the case increment start count if 0 + if (modemStartCount.value == 0) modemStartCount.value++; + } else { + // collapse settings screen + var collapseFirstRow = new bootstrap.Collapse( + document.getElementById("collapseFirstRow"), + { toggle: false }, + ); + collapseFirstRow.show(); + var collapseSecondRow = new bootstrap.Collapse( + document.getElementById("collapseSecondRow"), + { toggle: false }, + ); + collapseSecondRow.show(); + var collapseThirdRow = new bootstrap.Collapse( + document.getElementById("collapseThirdRow"), + { toggle: false }, + ); + collapseThirdRow.hide(); + var collapseFourthRow = new bootstrap.Collapse( + document.getElementById("collapseFourthRow"), + { toggle: false }, + ); + collapseFourthRow.hide(); + } + } + + return { + dxcallsign, + busy_state, + arq_state, + new_frequency, + frequency, + mode, + bandwidth, + dbfs_level, + dbfs_level_percent, + speed_level, + fft, + channel_busy, + channel_busy_slot, + getChannelBusySlotState, + scatter, + ptt_state, + s_meter_strength_percent, + s_meter_strength_raw, + arq_total_bytes, + audio_recording, + hamlib_status, + audio_level, + alc, + updateTncState, + arq_transmission_percent, + arq_speed_list_bpm, + arq_speed_list_timestamp, + arq_speed_list_snr, + arq_seconds_until_finish, + arq_seconds_until_timeout, + arq_seconds_until_timeout_percent, + modem_running_state, + arq_session_state, + is_codec2_traffic, + rf_level, + heard_stations, + beacon_state, + rigctld_started, + rigctld_process, + python_version, + modem_version, + rx_buffer_length, + }; +}); diff --git a/gui/src/style.css b/gui/src/style.css new file mode 100644 index 00000000..e69de29b diff --git a/gui/src/styles.css b/gui/src/styles.css index 1fc88516..79ba714b 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -107,3 +107,11 @@ https://stackoverflow.com/a/9622873 [data-bs-theme="dark"] { /* default dark theme mods */ } +.modal-backdrop { + background-color: transparent; +} + +.modal-backdrop.in { + filter: alpha(opacity=10); + opacity: .1 +} diff --git a/gui/src/vite-env.d.ts b/gui/src/vite-env.d.ts new file mode 100644 index 00000000..46e39beb --- /dev/null +++ b/gui/src/vite-env.d.ts @@ -0,0 +1,15 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly PACKAGE_VERSION: string; + // more env variables... +} +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file diff --git a/gui/src/waterfall/server.py b/gui/src/waterfall/server.py deleted file mode 100644 index a6402bc2..00000000 --- a/gui/src/waterfall/server.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2019 Jeppe Ledet-Pedersen -# This software is released under the MIT license. -# See the LICENSE file for further details. - -import sys -import json -import argparse - -from gnuradio import gr -from gnuradio import uhd -from gnuradio.fft import logpwrfft - -import numpy as np - -from gevent.pywsgi import WSGIServer -from geventwebsocket import WebSocketError -from geventwebsocket.handler import WebSocketHandler - -from bottle import request, Bottle, abort, static_file - - -app = Bottle() -connections = set() -opts = {} - - -@app.route('/websocket') -def handle_websocket(): - wsock = request.environ.get('wsgi.websocket') - if not wsock: - abort(400, 'Expected WebSocket request.') - - connections.add(wsock) - - # Send center frequency and span - wsock.send(json.dumps(opts)) - - while True: - try: - wsock.receive() - except WebSocketError: - break - - connections.remove(wsock) - - -@app.route('/') -def index(): - return static_file('index.html', root='.') - - -@app.route('/') -def static(filename): - return static_file(filename, root='.') - - -class fft_broadcast_sink(gr.sync_block): - def __init__(self, fft_size): - gr.sync_block.__init__(self, - name="plotter", - in_sig=[(np.float32, fft_size)], - out_sig=[]) - - def work(self, input_items, output_items): - ninput_items = len(input_items[0]) - - for bins in input_items[0]: - p = np.around(bins).astype(int) - p = np.fft.fftshift(p) - for c in connections.copy(): - try: - c.send(json.dumps({'s': p.tolist()}, separators=(',', ':'))) - except Exception: - connections.remove(c) - - self.consume(0, ninput_items) - - return 0 - - -class fft_receiver(gr.top_block): - def __init__(self, samp_rate, freq, gain, fft_size, framerate): - gr.top_block.__init__(self, "Top Block") - - self.usrp = uhd.usrp_source( - ",".join(("", "")), - uhd.stream_args( - cpu_format="fc32", - channels=range(1), - ), - ) - self.usrp.set_samp_rate(samp_rate) - self.usrp.set_center_freq(freq, 0) - self.usrp.set_gain(gain, 0) - - self.fft = logpwrfft.logpwrfft_c( - sample_rate=samp_rate, - fft_size=fft_size, - ref_scale=1, - frame_rate=framerate, - avg_alpha=1, - average=False, - ) - self.fft_broadcast = fft_broadcast_sink(fft_size) - - self.connect((self.fft, 0), (self.fft_broadcast, 0)) - self.connect((self.usrp, 0), (self.fft, 0)) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--sample-rate', type=float, default=40e6) - parser.add_argument('-f', '--frequency', type=float, default=940e6) - parser.add_argument('-g', '--gain', type=float, default=40) - parser.add_argument('-n', '--fft-size', type=int, default=4096) - parser.add_argument('-r', '--frame-rate', type=int, default=25) - - args = parser.parse_args() - - if gr.enable_realtime_scheduling() != gr.RT_OK or 0: - print("Error: failed to enable real-time scheduling.") - - tb = fft_receiver( - samp_rate=args.sample_rate, - freq=args.frequency, - gain=args.gain, - fft_size=args.fft_size, - framerate=args.frame_rate - ) - tb.start() - - opts['center'] = args.frequency - opts['span'] = args.sample_rate - - server = WSGIServer(("0.0.0.0", 8000), app, - handler_class=WebSocketHandler) - try: - server.serve_forever() - except Exception: - sys.exit(0) - - tb.stop() - tb.wait() - - -if __name__ == '__main__': - main() diff --git a/gui/src/waterfall/spectrogram.js b/gui/src/waterfall/spectrogram.js deleted file mode 100644 index 669432b5..00000000 --- a/gui/src/waterfall/spectrogram.js +++ /dev/null @@ -1,607 +0,0 @@ -/*============================================================= - Filename: Spectrogram-1v00.js - - JavaScript graphics functions to draw Spectrograms. - - Date Description By - -------|-------------------------------------------------|--- - 12Nov18 First beta ARC - 17Nov18 Added offset into data buffer ARC - 08May19 this.imageURL URL added - bugfix: fixed isNaN test - Changed sgStart, sgStop to start, stop - Added options object to constructors ARC - 10May19 Enabled Left to Right as well as Top to Bottom ARC - 11May19 Added RasterscanSVG ARC - 12May19 Added blnkline for horizontal ratser scans ARC - 13May19 Eliminated unneccessary putImageData ARC - 14May19 Removed toDataURL, not used drawImage is better - bugfix: SVG RHC names swapped ARC - 02Jun19 bugfix: startOfs not honored in horizontalNewLine ARC - 03Jun19 Flipped the SVG and RHC names for waterfalls ARC - 04Jun19 Unflip SVG and RHC for horizontal mode ARC - Swap "SVG" & "RHC" strings to match fn names ARC - 05Jun19 bugfix: WaterfallSVG scrolling wrong way ARC - 10Jun19 bugfix: support lineRate=0 for static display - bugfix: ipBufPtr must be a ptr to a ptr ARC - 11Jun19 Make ipBuffers an Array of Arrays, if lineRate=0 - use all buffers else use only ipBuffer[0] ARC - 13Jun19 Use Waterfall and Rasterscan plus direction - Use Boolean rater than string compare ARC - 16Jun19 Use const and let ARC - 20Jun19 Change order of parameters ARC - 21Jun19 Add setLineRate method ARC - 06Jul19 Released as Rev 1v00 ARC - ==============================================================*/ - -var Waterfall, Rasterscan; - -(function () { - Waterfall = function (ipBufAry, w, h, dir, options) { - var direction = typeof dir === "string" ? dir.toLowerCase() : "down"; - - switch (direction) { - case "up": - return new Spectrogram(ipBufAry, w, h, "WF", false, true, options); - case "down": - default: - return new Spectrogram(ipBufAry, w, h, "WF", true, true, options); - case "left": - return new Spectrogram(ipBufAry, w, h, "WF", false, false, options); - case "right": - return new Spectrogram(ipBufAry, w, h, "WF", true, false, options); - } - }; - - Rasterscan = function (ipBufAry, w, h, dir, options) { - const direction = typeof dir === "string" ? dir.toLowerCase() : "down"; - - switch (direction) { - case "up": - return new Spectrogram(ipBufAry, w, h, "RS", true, true, options); - case "down": - default: - return new Spectrogram(ipBufAry, w, h, "RS", false, true, options); - case "left": - return new Spectrogram(ipBufAry, w, h, "RS", false, false, options); - case "right": - return new Spectrogram(ipBufAry, w, h, "RS", true, false, options); - } - }; - - function Spectrogram(ipBufAry, w, h, sgMode, rhc, vert, options) { - const opt = typeof options === "object" ? options : {}; // avoid undeclared object errors - let offScreenCtx; // offscreen canvas drawing context - const pxPerLine = w || 200; - const lines = h || 200; - let lineRate = 30; // requested line rate for dynamic waterfalls - let interval = 0; // msec - let startOfs = 0; - const lineBuf = new ArrayBuffer(pxPerLine * 4); // 1 line - const lineBuf8 = new Uint8ClampedArray(lineBuf); - const lineImgData = new ImageData(lineBuf8, pxPerLine, 1); // 1 line of canvas pixels - let pageImgData; // lines * pxPerLine of canvas pixels - let ipBuf8; // map input data to 0..255 unsigned bytes - const blankBuf = new ArrayBuffer(pxPerLine * 4); // 1 line - const blankBuf8 = new Uint8ClampedArray(blankBuf); - const blankImgData = new ImageData(blankBuf8, pxPerLine, 1); // 1 line of canvas pixels - const clearBuf = new ArrayBuffer(pxPerLine * lines * 4); // fills with 0s ie. rgba 0,0,0,0 = transparent - const clearBuf8 = new Uint8ClampedArray(clearBuf); - let clearImgData; - let nextLine = 0; - let timerID = null; - let running = false; - let sgTime = 0; - let sgStartTime = 0; - - // Matlab Jet ref: stackoverflow.com grayscale-to-red-green-blue-matlab-jet-color-scale - let colMap = [ - [0, 0, 128, 255], - [0, 0, 131, 255], - [0, 0, 135, 255], - [0, 0, 139, 255], - [0, 0, 143, 255], - [0, 0, 147, 255], - [0, 0, 151, 255], - [0, 0, 155, 255], - [0, 0, 159, 255], - [0, 0, 163, 255], - [0, 0, 167, 255], - [0, 0, 171, 255], - [0, 0, 175, 255], - [0, 0, 179, 255], - [0, 0, 183, 255], - [0, 0, 187, 255], - [0, 0, 191, 255], - [0, 0, 195, 255], - [0, 0, 199, 255], - [0, 0, 203, 255], - [0, 0, 207, 255], - [0, 0, 211, 255], - [0, 0, 215, 255], - [0, 0, 219, 255], - [0, 0, 223, 255], - [0, 0, 227, 255], - [0, 0, 231, 255], - [0, 0, 235, 255], - [0, 0, 239, 255], - [0, 0, 243, 255], - [0, 0, 247, 255], - [0, 0, 251, 255], - [0, 0, 255, 255], - [0, 4, 255, 255], - [0, 8, 255, 255], - [0, 12, 255, 255], - [0, 16, 255, 255], - [0, 20, 255, 255], - [0, 24, 255, 255], - [0, 28, 255, 255], - [0, 32, 255, 255], - [0, 36, 255, 255], - [0, 40, 255, 255], - [0, 44, 255, 255], - [0, 48, 255, 255], - [0, 52, 255, 255], - [0, 56, 255, 255], - [0, 60, 255, 255], - [0, 64, 255, 255], - [0, 68, 255, 255], - [0, 72, 255, 255], - [0, 76, 255, 255], - [0, 80, 255, 255], - [0, 84, 255, 255], - [0, 88, 255, 255], - [0, 92, 255, 255], - [0, 96, 255, 255], - [0, 100, 255, 255], - [0, 104, 255, 255], - [0, 108, 255, 255], - [0, 112, 255, 255], - [0, 116, 255, 255], - [0, 120, 255, 255], - [0, 124, 255, 255], - [0, 128, 255, 255], - [0, 131, 255, 255], - [0, 135, 255, 255], - [0, 139, 255, 255], - [0, 143, 255, 255], - [0, 147, 255, 255], - [0, 151, 255, 255], - [0, 155, 255, 255], - [0, 159, 255, 255], - [0, 163, 255, 255], - [0, 167, 255, 255], - [0, 171, 255, 255], - [0, 175, 255, 255], - [0, 179, 255, 255], - [0, 183, 255, 255], - [0, 187, 255, 255], - [0, 191, 255, 255], - [0, 195, 255, 255], - [0, 199, 255, 255], - [0, 203, 255, 255], - [0, 207, 255, 255], - [0, 211, 255, 255], - [0, 215, 255, 255], - [0, 219, 255, 255], - [0, 223, 255, 255], - [0, 227, 255, 255], - [0, 231, 255, 255], - [0, 235, 255, 255], - [0, 239, 255, 255], - [0, 243, 255, 255], - [0, 247, 255, 255], - [0, 251, 255, 255], - [0, 255, 255, 255], - [4, 255, 251, 255], - [8, 255, 247, 255], - [12, 255, 243, 255], - [16, 255, 239, 255], - [20, 255, 235, 255], - [24, 255, 231, 255], - [28, 255, 227, 255], - [32, 255, 223, 255], - [36, 255, 219, 255], - [40, 255, 215, 255], - [44, 255, 211, 255], - [48, 255, 207, 255], - [52, 255, 203, 255], - [56, 255, 199, 255], - [60, 255, 195, 255], - [64, 255, 191, 255], - [68, 255, 187, 255], - [72, 255, 183, 255], - [76, 255, 179, 255], - [80, 255, 175, 255], - [84, 255, 171, 255], - [88, 255, 167, 255], - [92, 255, 163, 255], - [96, 255, 159, 255], - [100, 255, 155, 255], - [104, 255, 151, 255], - [108, 255, 147, 255], - [112, 255, 143, 255], - [116, 255, 139, 255], - [120, 255, 135, 255], - [124, 255, 131, 255], - [128, 255, 128, 255], - [131, 255, 124, 255], - [135, 255, 120, 255], - [139, 255, 116, 255], - [143, 255, 112, 255], - [147, 255, 108, 255], - [151, 255, 104, 255], - [155, 255, 100, 255], - [159, 255, 96, 255], - [163, 255, 92, 255], - [167, 255, 88, 255], - [171, 255, 84, 255], - [175, 255, 80, 255], - [179, 255, 76, 255], - [183, 255, 72, 255], - [187, 255, 68, 255], - [191, 255, 64, 255], - [195, 255, 60, 255], - [199, 255, 56, 255], - [203, 255, 52, 255], - [207, 255, 48, 255], - [211, 255, 44, 255], - [215, 255, 40, 255], - [219, 255, 36, 255], - [223, 255, 32, 255], - [227, 255, 28, 255], - [231, 255, 24, 255], - [235, 255, 20, 255], - [239, 255, 16, 255], - [243, 255, 12, 255], - [247, 255, 8, 255], - [251, 255, 4, 255], - [255, 255, 0, 255], - [255, 251, 0, 255], - [255, 247, 0, 255], - [255, 243, 0, 255], - [255, 239, 0, 255], - [255, 235, 0, 255], - [255, 231, 0, 255], - [255, 227, 0, 255], - [255, 223, 0, 255], - [255, 219, 0, 255], - [255, 215, 0, 255], - [255, 211, 0, 255], - [255, 207, 0, 255], - [255, 203, 0, 255], - [255, 199, 0, 255], - [255, 195, 0, 255], - [255, 191, 0, 255], - [255, 187, 0, 255], - [255, 183, 0, 255], - [255, 179, 0, 255], - [255, 175, 0, 255], - [255, 171, 0, 255], - [255, 167, 0, 255], - [255, 163, 0, 255], - [255, 159, 0, 255], - [255, 155, 0, 255], - [255, 151, 0, 255], - [255, 147, 0, 255], - [255, 143, 0, 255], - [255, 139, 0, 255], - [255, 135, 0, 255], - [255, 131, 0, 255], - [255, 128, 0, 255], - [255, 124, 0, 255], - [255, 120, 0, 255], - [255, 116, 0, 255], - [255, 112, 0, 255], - [255, 108, 0, 255], - [255, 104, 0, 255], - [255, 100, 0, 255], - [255, 96, 0, 255], - [255, 92, 0, 255], - [255, 88, 0, 255], - [255, 84, 0, 255], - [255, 80, 0, 255], - [255, 76, 0, 255], - [255, 72, 0, 255], - [255, 68, 0, 255], - [255, 64, 0, 255], - [255, 60, 0, 255], - [255, 56, 0, 255], - [255, 52, 0, 255], - [255, 48, 0, 255], - [255, 44, 0, 255], - [255, 40, 0, 255], - [255, 36, 0, 255], - [255, 32, 0, 255], - [255, 28, 0, 255], - [255, 24, 0, 255], - [255, 20, 0, 255], - [255, 16, 0, 255], - [255, 12, 0, 255], - [255, 8, 0, 255], - [255, 4, 0, 255], - [255, 0, 0, 255], - [251, 0, 0, 255], - [247, 0, 0, 255], - [243, 0, 0, 255], - [239, 0, 0, 255], - [235, 0, 0, 255], - [231, 0, 0, 255], - [227, 0, 0, 255], - [223, 0, 0, 255], - [219, 0, 0, 255], - [215, 0, 0, 255], - [211, 0, 0, 255], - [207, 0, 0, 255], - [203, 0, 0, 255], - [199, 0, 0, 255], - [195, 0, 0, 255], - [191, 0, 0, 255], - [187, 0, 0, 255], - [183, 0, 0, 255], - [179, 0, 0, 255], - [175, 0, 0, 255], - [171, 0, 0, 255], - [167, 0, 0, 255], - [163, 0, 0, 255], - [159, 0, 0, 255], - [155, 0, 0, 255], - [151, 0, 0, 255], - [147, 0, 0, 255], - [143, 0, 0, 255], - [139, 0, 0, 255], - [135, 0, 0, 255], - [131, 0, 0, 255], - [0, 0, 0, 0], - ]; - - function incrLine() { - if ((vert && !rhc) || (!vert && rhc)) { - nextLine++; - if (nextLine >= lines) { - nextLine = 0; - } - } else { - nextLine--; - if (nextLine < 0) { - nextLine = lines - 1; - } - } - } - - function updateWaterfall() { - // update dynamic waterfalls at a fixed rate - let sgDiff; - - // grab latest line of data, write it to off screen buffer, inc 'nextLine' - sgNewLine(); - // loop to write data data at the desired rate, data is being updated asynchronously - // ref for accurate timeout: http://www.sitepoint.com/creating-accurate-timers-in-javascript - sgTime += interval; - sgDiff = Date.now() - sgStartTime - sgTime; - if (running) { - timerID = setTimeout(updateWaterfall, interval - sgDiff); - } - } - - function sgSetLineRate(newRate) { - if (isNaN(newRate) || newRate > 50 || newRate < 0) { - console.error("invalid line rate [0 <= lineRate < 50 lines/sec]"); - // don't change the lineRate; - } else if (newRate === 0) { - // static (one pass) raster - lineRate = 0; - } else { - lineRate = newRate; - interval = 1000 / lineRate; // msec - } - } - - this.setLineRate = sgSetLineRate; - - function setProperty(propertyName, value) { - if (typeof propertyName !== "string" || value === undefined) { - // null is OK, forces default - return; - } - switch (propertyName.toLowerCase()) { - case "linerate": - sgSetLineRate(value); // setLine does checks for number etc - break; - case "startbin": - if (!isNaN(value) && value > 0) { - startOfs = value; - } - break; - case "onscreenparentid": - if (typeof value === "string" && document.getElementById(value)) { - demoCvsId = value; - } - break; - case "colormap": - if ( - Array.isArray(value) && - Array.isArray(value[0]) && - value[0].length == 4 - ) { - colMap = value; // value must be an array of 4 element arrays to get here - if (colMap.length < 256) { - // fill out the remaining colors with last color - for (let i = colMap.length; i < 256; i++) { - colMap[i] = colMap[colMap.length - 1]; - } - } - } - break; - default: - break; - } - } - - function verticalNewLine() { - let tmpImgData, ipBuf8; - - if (sgMode == "WF") { - if (rhc) { - // shift the current display down 1 line, oldest line drops off - tmpImgData = offScreenCtx.getImageData(0, 0, pxPerLine, lines - 1); - offScreenCtx.putImageData(tmpImgData, 0, 1); - } else { - // shift the current display up 1 line, oldest line drops off - tmpImgData = offScreenCtx.getImageData(0, 1, pxPerLine, lines - 1); - offScreenCtx.putImageData(tmpImgData, 0, 0); - } - } - ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]); - for ( - let sigVal, rgba, opIdx = 0, ipIdx = startOfs; - ipIdx < pxPerLine + startOfs; - opIdx += 4, ipIdx++ - ) { - sigVal = ipBuf8[ipIdx] || 0; // if input line too short add zeros - rgba = colMap[sigVal]; // array of rgba values - // byte reverse so number aa bb gg rr - lineBuf8[opIdx] = rgba[0]; // red - lineBuf8[opIdx + 1] = rgba[1]; // green - lineBuf8[opIdx + 2] = rgba[2]; // blue - lineBuf8[opIdx + 3] = rgba[3]; // alpha - } - offScreenCtx.putImageData(lineImgData, 0, nextLine); - if (sgMode === "RS") { - incrLine(); - // if not static draw a white line in front of the current line to indicate new data point - if (lineRate) { - offScreenCtx.putImageData(blankImgData, 0, nextLine); - } - } - } - - function horizontalNewLine() { - let tmpImgData, ipBuf8; - - if (sgMode == "WF") { - if (rhc) { - // shift the current display right 1 line, oldest line drops off - tmpImgData = offScreenCtx.getImageData(0, 0, lines - 1, pxPerLine); - offScreenCtx.putImageData(tmpImgData, 1, 0); - } else { - // shift the current display left 1 line, oldest line drops off - tmpImgData = offScreenCtx.getImageData(1, 0, lines - 1, pxPerLine); - offScreenCtx.putImageData(tmpImgData, 0, 0); - } - } - // refresh the page image (it was just shifted) - pageImgData = offScreenCtx.getImageData(0, 0, lines, pxPerLine); - if (ipBufAry[0].constructor !== Uint8Array) { - ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]); // clamp input values to 0..255 range - } else { - ipBuf8 = ipBufAry[0]; // conversion already done - } - - for (let sigVal, rgba, opIdx, ipIdx = 0; ipIdx < pxPerLine; ipIdx++) { - sigVal = ipBuf8[ipIdx + startOfs] || 0; // if input line too short add zeros - rgba = colMap[sigVal]; // array of rgba values - opIdx = 4 * ((pxPerLine - ipIdx - 1) * lines + nextLine); - // byte reverse so number aa bb gg rr - pageImgData.data[opIdx] = rgba[0]; // red - pageImgData.data[opIdx + 1] = rgba[1]; // green - pageImgData.data[opIdx + 2] = rgba[2]; // blue - pageImgData.data[opIdx + 3] = rgba[3]; // alpha - } - if (sgMode === "RS") { - incrLine(); - // if not draw a white line in front of the current line to indicate new data point - if (lineRate) { - for (let j = 0; j < pxPerLine; j++) { - if (rhc) { - opIdx = 4 * (j * lines + nextLine); - } else { - opIdx = 4 * ((pxPerLine - j - 1) * lines + nextLine); - } - // byte reverse so number aa bb gg rr - pageImgData.data[opIdx] = 255; // red - pageImgData.data[opIdx + 1] = 255; // green - pageImgData.data[opIdx + 2] = 255; // blue - pageImgData.data[opIdx + 3] = 255; // alpha - } - } - } - offScreenCtx.putImageData(pageImgData, 0, 0); - } - - const sgNewLine = vert ? verticalNewLine : horizontalNewLine; // function pointers - - //===== set all the options ================ - for (let prop in opt) { - // check that this is opt's own property, not inherited from prototype - if (opt.hasOwnProperty(prop)) { - setProperty(prop, opt[prop]); - } - } - - // ===== now make the exposed properties and methods =============== - this.newLine = sgNewLine; - - this.offScreenCvs = document.createElement("canvas"); - if (vert) { - this.offScreenCvs.setAttribute("width", pxPerLine); // reset canvas pixels width - this.offScreenCvs.setAttribute("height", lines); // don't use style for this - clearImgData = new ImageData(clearBuf8, pxPerLine, lines); - } // data written in columns - else { - this.offScreenCvs.setAttribute("width", lines); // reset canvas pixels width - this.offScreenCvs.setAttribute("height", pxPerLine); // don't use style for this - clearImgData = new ImageData(clearBuf8, lines, pxPerLine); - } - offScreenCtx = this.offScreenCvs.getContext("2d"); - - this.clear = function () { - offScreenCtx.putImageData(clearImgData, 0, 0); - }; - - this.start = function () { - sgStartTime = Date.now(); - sgTime = 0; - running = true; - updateWaterfall(); // start the update loop - }; - - this.stop = function () { - running = false; - if (timerID) { - clearTimeout(timerID); - } - // reset where the next line is to be written - if (sgMode === "RS") { - if (vert) { - nextLine = rhc ? lines - 1 : 0; - } else { - nextLine = rhc ? 0 : lines - 1; - } - } // WF - else { - nextLine = rhc ? 0 : lines - 1; - } - }; - - // make a white line, it will show the input line for RS displays - blankBuf8.fill(255); - // make a full canvas of the color map 0 values - for (let i = 0; i < pxPerLine * lines * 4; i += 4) { - // byte reverse so number aa bb gg rr - clearBuf8[i] = colMap[0][0]; // red - clearBuf8[i + 1] = colMap[0][1]; // green - clearBuf8[i + 2] = colMap[0][2]; // blue - clearBuf8[i + 3] = colMap[0][3]; // alpha - } - // for diagnostics only - if (typeof demoCvsId == "string") { - document.getElementById(demoCvsId).appendChild(this.offScreenCvs); - } - // initialize the direction and first line position - this.stop(); - - // everything is set - // if dynamic, wait for the start or newLine methods to be called - } -})(); diff --git a/gui/src/waterfall/spectrum.js b/gui/src/waterfall/spectrum.js deleted file mode 100644 index f3976bd9..00000000 --- a/gui/src/waterfall/spectrum.js +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (c) 2019 Jeppe Ledet-Pedersen - * This software is released under the MIT license. - * See the LICENSE file for further details. - */ - -"use strict"; - -Spectrum.prototype.squeeze = function (value, out_min, out_max) { - if (value <= this.min_db) return out_min; - else if (value >= this.max_db) return out_max; - else - return Math.round( - ((value - this.min_db) / (this.max_db - this.min_db)) * out_max - ); -}; - -Spectrum.prototype.rowToImageData = function (bins) { - for (var i = 0; i < this.imagedata.data.length; i += 4) { - var cindex = this.squeeze(bins[i / 4], 0, 255); - var color = this.colormap[cindex]; - this.imagedata.data[i + 0] = color[0]; - this.imagedata.data[i + 1] = color[1]; - this.imagedata.data[i + 2] = color[2]; - this.imagedata.data[i + 3] = 255; - } -}; - -Spectrum.prototype.addWaterfallRow = function (bins) { - // Shift waterfall 1 row down - this.ctx_wf.drawImage( - this.ctx_wf.canvas, - 0, - 0, - this.wf_size, - this.wf_rows - 1, - 0, - 1, - this.wf_size, - this.wf_rows - 1 - ); - - // Draw new line on waterfall canvas - this.rowToImageData(bins); - this.ctx_wf.putImageData(this.imagedata, 0, 0); - - var width = this.ctx.canvas.width; - var height = this.ctx.canvas.height; - - // Copy scaled FFT canvas to screen. Only copy the number of rows that will - // fit in waterfall area to avoid vertical scaling. - this.ctx.imageSmoothingEnabled = false; - var rows = Math.min(this.wf_rows, height - this.spectrumHeight); - this.ctx.drawImage( - this.ctx_wf.canvas, - 0, - 0, - this.wf_size, - rows, - 0, - this.spectrumHeight, - width, - height - this.spectrumHeight - ); -}; - -Spectrum.prototype.drawFFT = function (bins) { - this.ctx.beginPath(); - this.ctx.moveTo(-1, this.spectrumHeight + 1); - for (var i = 0; i < bins.length; i++) { - var y = this.spectrumHeight - this.squeeze(bins[i], 0, this.spectrumHeight); - if (y > this.spectrumHeight - 1) y = this.spectrumHeight + 1; // Hide underflow - if (y < 0) y = 0; - if (i == 0) this.ctx.lineTo(-1, y); - this.ctx.lineTo(i, y); - if (i == bins.length - 1) this.ctx.lineTo(this.wf_size + 1, y); - } - this.ctx.lineTo(this.wf_size + 1, this.spectrumHeight + 1); - this.ctx.strokeStyle = "#fefefe"; - this.ctx.stroke(); -}; - -//Spectrum.prototype.drawSpectrum = function(bins) { -Spectrum.prototype.drawSpectrum = function () { - var width = this.ctx.canvas.width; - var height = this.ctx.canvas.height; - - // Modification by DJ2LS - // Draw bandwidth lines - // TODO: Math not correct. But a first attempt - // it seems position is more or less equal to frequenzy by factor 10 - // eg. position 150 == 1500Hz - /* - // CENTER LINE - this.ctx_wf.beginPath(); - this.ctx_wf.moveTo(150,0); - this.ctx_wf.lineTo(150, height); - this.ctx_wf.lineWidth = 1; - this.ctx_wf.strokeStyle = '#8C8C8C'; - this.ctx_wf.stroke() - */ - - // 586Hz and 1700Hz LINES - var linePositionLow = 121.6; //150 - bandwith/20 - var linePositionHigh = 178.4; //150 + bandwidth/20 - var linePositionLow2 = 65; //150 - bandwith/20 - var linePositionHigh2 = 235; //150 + bandwith/20 - this.ctx_wf.beginPath(); - this.ctx_wf.moveTo(linePositionLow, 0); - this.ctx_wf.lineTo(linePositionLow, height); - this.ctx_wf.moveTo(linePositionHigh, 0); - this.ctx_wf.lineTo(linePositionHigh, height); - this.ctx_wf.moveTo(linePositionLow2, 0); - this.ctx_wf.lineTo(linePositionLow2, height); - this.ctx_wf.moveTo(linePositionHigh2, 0); - this.ctx_wf.lineTo(linePositionHigh2, height); - this.ctx_wf.lineWidth = 1; - this.ctx_wf.strokeStyle = "#C3C3C3"; - this.ctx_wf.stroke(); - - // ---- END OF MODIFICATION ------ - - // Fill with black - this.ctx.fillStyle = "white"; - this.ctx.fillRect(0, 0, width, height); - - //Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements - return; - /* - // FFT averaging - if (this.averaging > 0) { - if (!this.binsAverage || this.binsAverage.length != bins.length) { - this.binsAverage = Array.from(bins); - } else { - for (var i = 0; i < bins.length; i++) { - this.binsAverage[i] += this.alpha * (bins[i] - this.binsAverage[i]); - } - } - bins = this.binsAverage; - } - - // Max hold - if (this.maxHold) { - if (!this.binsMax || this.binsMax.length != bins.length) { - this.binsMax = Array.from(bins); - } else { - for (var i = 0; i < bins.length; i++) { - if (bins[i] > this.binsMax[i]) { - this.binsMax[i] = bins[i]; - } else { - // Decay - this.binsMax[i] = 1.0025 * this.binsMax[i]; - } - } - } - } - - // Do not draw anything if spectrum is not visible - if (this.ctx_axes.canvas.height < 1) - return; - - // Scale for FFT - this.ctx.save(); - this.ctx.scale(width / this.wf_size, 1); - - // Draw maxhold - if (this.maxHold) - this.drawFFT(this.binsMax); - - // Draw FFT bins - this.drawFFT(bins); - - // Restore scale - this.ctx.restore(); - - // Fill scaled path - this.ctx.fillStyle = this.gradient; - this.ctx.fill(); - - // 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 () { - var width = this.ctx_axes.canvas.width; - var height = this.ctx_axes.canvas.height; - - // Clear axes canvas - this.ctx_axes.clearRect(0, 0, width, height); - - // Draw axes - this.ctx_axes.font = "12px sans-serif"; - this.ctx_axes.fillStyle = "white"; - this.ctx_axes.textBaseline = "middle"; - - this.ctx_axes.textAlign = "left"; - var step = 10; - for (var i = this.min_db + 10; i <= this.max_db - 10; i += step) { - var y = height - this.squeeze(i, 0, height); - this.ctx_axes.fillText(i, 5, y); - - this.ctx_axes.beginPath(); - this.ctx_axes.moveTo(20, y); - this.ctx_axes.lineTo(width, y); - this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)"; - this.ctx_axes.stroke(); - } - - this.ctx_axes.textBaseline = "bottom"; - for (var i = 0; i < 11; i++) { - var x = Math.round(width / 10) * i; - - if (this.spanHz > 0) { - var adjust = 0; - if (i == 0) { - this.ctx_axes.textAlign = "left"; - adjust = 3; - } else if (i == 10) { - this.ctx_axes.textAlign = "right"; - adjust = -3; - } else { - this.ctx_axes.textAlign = "center"; - } - - var freq = this.centerHz + (this.spanHz / 10) * (i - 5); - if (this.centerHz + this.spanHz > 1e6) freq = freq / 1e6 + "M"; - else if (this.centerHz + this.spanHz > 1e3) freq = freq / 1e3 + "k"; - this.ctx_axes.fillText(freq, x + adjust, height - 3); - } - - this.ctx_axes.beginPath(); - this.ctx_axes.moveTo(x, 0); - this.ctx_axes.lineTo(x, height); - this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)"; - this.ctx_axes.stroke(); - } -}; - -Spectrum.prototype.addData = function (data) { - if (!this.paused) { - if (data.length != this.wf_size) { - this.wf_size = data.length; - this.ctx_wf.canvas.width = data.length; - this.ctx_wf.fillStyle = "white"; - 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(); - this.addWaterfallRow(data); - this.resize(); - } -}; - -Spectrum.prototype.updateSpectrumRatio = function () { - this.spectrumHeight = Math.round( - (this.canvas.height * this.spectrumPercent) / 100.0 - ); - - this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight); - for (var i = 0; i < this.colormap.length; i++) { - var c = this.colormap[this.colormap.length - 1 - i]; - this.gradient.addColorStop( - i / this.colormap.length, - "rgba(" + c[0] + "," + c[1] + "," + c[2] + ", 1.0)" - ); - } -}; - -Spectrum.prototype.resize = function () { - var width = this.canvas.clientWidth; - var height = this.canvas.clientHeight; - - if (this.canvas.width != width || this.canvas.height != height) { - this.canvas.width = width; - this.canvas.height = height; - this.updateSpectrumRatio(); - } - - if (this.axes.width != width || this.axes.height != this.spectrumHeight) { - this.axes.width = width; - this.axes.height = this.spectrumHeight; - this.updateAxes(); - } -}; - -Spectrum.prototype.setSpectrumPercent = function (percent) { - if (percent >= 0 && percent <= 100) { - this.spectrumPercent = percent; - this.updateSpectrumRatio(); - } -}; - -Spectrum.prototype.incrementSpectrumPercent = function () { - if (this.spectrumPercent + this.spectrumPercentStep <= 100) { - this.setSpectrumPercent(this.spectrumPercent + this.spectrumPercentStep); - } -}; - -Spectrum.prototype.decrementSpectrumPercent = function () { - if (this.spectrumPercent - this.spectrumPercentStep >= 0) { - this.setSpectrumPercent(this.spectrumPercent - this.spectrumPercentStep); - } -}; - -Spectrum.prototype.toggleColor = function () { - this.colorindex++; - if (this.colorindex >= colormaps.length) this.colorindex = 0; - this.colormap = colormaps[this.colorindex]; - this.updateSpectrumRatio(); -}; - -Spectrum.prototype.setRange = function (min_db, max_db) { - this.min_db = min_db; - this.max_db = max_db; - this.updateAxes(); -}; - -Spectrum.prototype.rangeUp = function () { - this.setRange(this.min_db - 5, this.max_db - 5); -}; - -Spectrum.prototype.rangeDown = function () { - this.setRange(this.min_db + 5, this.max_db + 5); -}; - -Spectrum.prototype.rangeIncrease = function () { - this.setRange(this.min_db - 5, this.max_db + 5); -}; - -Spectrum.prototype.rangeDecrease = function () { - if (this.max_db - this.min_db > 10) - this.setRange(this.min_db + 5, this.max_db - 5); -}; - -Spectrum.prototype.setCenterHz = function (hz) { - this.centerHz = hz; - this.updateAxes(); -}; - -Spectrum.prototype.setSpanHz = function (hz) { - this.spanHz = hz; - this.updateAxes(); -}; - -Spectrum.prototype.setAveraging = function (num) { - if (num >= 0) { - this.averaging = num; - this.alpha = 2 / (this.averaging + 1); - } -}; - -Spectrum.prototype.incrementAveraging = function () { - this.setAveraging(this.averaging + 1); -}; - -Spectrum.prototype.decrementAveraging = function () { - if (this.averaging > 0) { - this.setAveraging(this.averaging - 1); - } -}; - -Spectrum.prototype.setPaused = function (paused) { - this.paused = paused; -}; - -Spectrum.prototype.togglePaused = function () { - this.setPaused(!this.paused); -}; - -Spectrum.prototype.setMaxHold = function (maxhold) { - this.maxHold = maxhold; - this.binsMax = undefined; -}; - -Spectrum.prototype.toggleMaxHold = function () { - this.setMaxHold(!this.maxHold); -}; - -Spectrum.prototype.toggleFullscreen = function () { - if (!this.fullscreen) { - if (this.canvas.requestFullscreen) { - this.canvas.requestFullscreen(); - } else if (this.canvas.mozRequestFullScreen) { - this.canvas.mozRequestFullScreen(); - } else if (this.canvas.webkitRequestFullscreen) { - this.canvas.webkitRequestFullscreen(); - } else if (this.canvas.msRequestFullscreen) { - this.canvas.msRequestFullscreen(); - } - this.fullscreen = true; - } else { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } - this.fullscreen = false; - } -}; - -Spectrum.prototype.onKeypress = function (e) { - if (e.key == " ") { - this.togglePaused(); - } else if (e.key == "f") { - this.toggleFullscreen(); - } else if (e.key == "c") { - this.toggleColor(); - } else if (e.key == "ArrowUp") { - this.rangeUp(); - } else if (e.key == "ArrowDown") { - this.rangeDown(); - } else if (e.key == "ArrowLeft") { - this.rangeDecrease(); - } else if (e.key == "ArrowRight") { - this.rangeIncrease(); - } else if (e.key == "s") { - this.incrementSpectrumPercent(); - } else if (e.key == "w") { - this.decrementSpectrumPercent(); - } else if (e.key == "+") { - this.incrementAveraging(); - } else if (e.key == "-") { - this.decrementAveraging(); - } else if (e.key == "m") { - this.toggleMaxHold(); - } -}; - -function Spectrum(id, options) { - // Handle options - this.centerHz = options && options.centerHz ? options.centerHz : 1500; - this.spanHz = options && options.spanHz ? options.spanHz : 0; - this.wf_size = options && options.wf_size ? options.wf_size : 0; - this.wf_rows = options && options.wf_rows ? options.wf_rows : 1024; - this.spectrumPercent = - options && options.spectrumPercent ? options.spectrumPercent : 0; - this.spectrumPercentStep = - options && options.spectrumPercentStep ? options.spectrumPercentStep : 0; - this.averaging = options && options.averaging ? options.averaging : 0; - this.maxHold = options && options.maxHold ? options.maxHold : false; - - // Setup state - this.paused = false; - this.fullscreen = false; - this.min_db = 0; - this.max_db = 70; - this.spectrumHeight = 0; - - // Colors - this.colorindex = 0; - this.colormap = colormaps[2]; - - // Create main canvas and adjust dimensions to match actual - this.canvas = document.getElementById(id); - this.canvas.height = this.canvas.clientHeight; - this.canvas.width = this.canvas.clientWidth; - this.ctx = this.canvas.getContext("2d"); - this.ctx.fillStyle = "white"; - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - - // Create offscreen canvas for axes - this.axes = document.createElement("canvas"); - this.axes.height = 1; // Updated later - this.axes.width = this.canvas.width; - this.ctx_axes = this.axes.getContext("2d"); - - // Create offscreen canvas for waterfall - this.wf = document.createElement("canvas"); - this.wf.height = this.wf_rows; - this.wf.width = this.wf_size; - this.ctx_wf = this.wf.getContext("2d"); - - // Trigger first render - this.setAveraging(this.averaging); - this.updateSpectrumRatio(); - this.resize(); -} diff --git a/gui/tsconfig.json b/gui/tsconfig.json new file mode 100644 index 00000000..f9dbec90 --- /dev/null +++ b/gui/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": false, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true, + "allowJs": true, + "noImplicitAny": false + }, + "include": ["src"], + "exclude": ["node_modules"], + "references": [ + { "path": "./tsconfig.node.json" } + ] +} + + + + + + + + + + + + + + + + diff --git a/gui/tsconfig.node.json b/gui/tsconfig.node.json new file mode 100644 index 00000000..ed1b5866 --- /dev/null +++ b/gui/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts", "package.json", "electron"] +} diff --git a/gui/vite.config.ts b/gui/vite.config.ts new file mode 100644 index 00000000..9933d2cd --- /dev/null +++ b/gui/vite.config.ts @@ -0,0 +1,94 @@ +import { rmSync } from "node:fs"; +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import electron from "vite-plugin-electron"; +import renderer from "vite-plugin-electron-renderer"; +import { notBundle } from "vite-plugin-electron/plugin"; +import pkg from "./package.json"; + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + rmSync("dist-electron", { recursive: true, force: true }); + + const isServe = command === "serve"; + const isBuild = command === "build"; + const sourcemap = isServe || !!process.env.VSCODE_DEBUG; + + return { + plugins: [ + vue(), + electron([ + { + // Main process entry file of the Electron App. + entry: "electron/main/index.ts", + onstart({ startup }) { + if (process.env.VSCODE_DEBUG) { + console.log( + /* For `.vscode/.debug.script.mjs` */ "[startup] Electron App", + ); + } else { + startup(); + } + }, + vite: { + build: { + sourcemap, + minify: isBuild, + outDir: "dist-electron/main", + rollupOptions: { + // Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons, + // we can use `external` to exclude them to ensure they work correctly. + // Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built. + // Of course, this is not absolute, just this way is relatively simple. :) + external: Object.keys( + "dependencies" in pkg ? pkg.dependencies : {}, + ), + }, + }, + plugins: [ + // This is just an option to improve build performance, it's non-deterministic! + // e.g. `import log from 'electron-log'` -> `const log = require('electron-log')` + isServe && notBundle(), + ], + }, + }, + { + entry: "electron/preload/index.ts", + onstart({ reload }) { + // Notify the Renderer process to reload the page when the Preload scripts build is complete, + // instead of restarting the entire Electron App. + reload(); + }, + vite: { + build: { + sourcemap: sourcemap ? "inline" : undefined, // #332 + minify: isBuild, + outDir: "dist-electron/preload", + rollupOptions: { + external: Object.keys( + "dependencies" in pkg ? pkg.dependencies : {}, + ), + }, + }, + plugins: [isServe && notBundle()], + }, + }, + ]), + // Use Node.js API in the Renderer process + renderer(), + ], + server: + process.env.VSCODE_DEBUG && + (() => { + const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL); + return { + host: url.hostname, + port: +url.port, + }; + })(), + define: { + "import.meta.env.PACKAGE_VERSION": JSON.stringify(pkg.version), + }, + clearScreen: false, + }; +}); diff --git a/tnc/.gitignore b/modem/.gitignore similarity index 100% rename from tnc/.gitignore rename to modem/.gitignore diff --git a/tnc/audio.py b/modem/audio.py similarity index 100% rename from tnc/audio.py rename to modem/audio.py diff --git a/tnc/broadcast.py b/modem/broadcast.py similarity index 85% rename from tnc/broadcast.py rename to modem/broadcast.py index 0d6a5b41..71dd2882 100644 --- a/tnc/broadcast.py +++ b/modem/broadcast.py @@ -4,7 +4,7 @@ import helpers import time import modem import base64 -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import sock import ujson as json @@ -34,7 +34,7 @@ class broadcastHandler: modem.RECEIVE_DATAC4 = True self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", fec="wakeup", mode=self.wakeup_mode, bursts=bursts, @@ -42,7 +42,7 @@ class broadcastHandler: ) self.log.info( - "[TNC] FRAME WAKEUP RCVD [" + "[Modem] FRAME WAKEUP RCVD [" + str(self.fec_wakeup_callsign, "UTF-8") + "] ", mode=self.wakeup_mode, bursts=bursts, ) @@ -51,13 +51,13 @@ class broadcastHandler: print(self.fec_wakeup_callsign) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", fec="broadcast", dxcallsign=str(self.fec_wakeup_callsign, "UTF-8"), data=base64.b64encode(data_in[1:]).decode("UTF-8") ) - self.log.info("[TNC] FEC DATA RCVD") + self.log.info("[Modem] FEC DATA RCVD") def send_data_to_socket_queue(self, **jsondata): """ @@ -67,7 +67,7 @@ class broadcastHandler: Dictionary containing the data to be sent, in the format: key=value, for each item. E.g.: self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="received", status="success", uuid=self.transmission_uuid, @@ -87,12 +87,12 @@ class broadcastHandler: if "dxcallsign" not in jsondata: jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8") except Exception as e: - self.log.debug("[TNC] error adding callsigns to network message", e=e) + self.log.debug("[Modem] error adding callsigns to network message", e=e) # run json dumps json_data_out = json.dumps(jsondata) - self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=json_data_out) + self.log.debug("[Modem] send_data_to_socket_queue:", jsondata=json_data_out) # finally push data to our network queue sock.SOCKET_QUEUE.put(json_data_out) @@ -106,11 +106,11 @@ class broadcastHandler: self.broadcast_timeout_reached = True self.log.info( - "[TNC] closing broadcast slot [" + "[Modem] closing broadcast slot [" + str(self.fec_wakeup_callsign, "UTF-8") + "] ", mode=self.wakeup_mode, bursts=self.broadcast_payload_bursts, ) - # TODO: We need a dynamic way of modifying this + # TODO We need a dynamic way of modifying this modem.RECEIVE_DATAC4 = False self.fec_wakeup_callsign = bytes() self.wakeup_received = False diff --git a/modem/callsign_reverse_db.txt b/modem/callsign_reverse_db.txt new file mode 100644 index 00000000..d3e169f7 --- /dev/null +++ b/modem/callsign_reverse_db.txt @@ -0,0 +1 @@ +{"DJ2LS-0": "22864b", "IW2DHW-0": "fcef94", "BEACON": "5492c7", "EI7IG-0": "0975c8", "G0HWW-0": "2cb363", "LA3QMA-0": "2b9fac", "EA7KOH-0": "9e1c3e", "OK6MS-0": "5f75ed", "N1QM-0": "e045a0"} \ No newline at end of file diff --git a/tnc/codec2.py b/modem/codec2.py similarity index 98% rename from tnc/codec2.py rename to modem/codec2.py index 12172be1..c3cee71c 100644 --- a/tnc/codec2.py +++ b/modem/codec2.py @@ -84,7 +84,7 @@ def freedv_get_mode_name_by_value(mode: int) -> str: #else: sys.path.append(os.path.abspath(".")) -log.info("[C2 ] Searching for libcodec2...") +#log.info("[C2 ] Searching for libcodec2...") if sys.platform == "linux": files = glob.glob(r"**/*libcodec2*", recursive=True) files.append("libcodec2.so") @@ -102,14 +102,14 @@ api = None for file in files: try: api = ctypes.CDLL(file) - log.info("[C2 ] Libcodec2 loaded", path=file) + #log.info("[C2 ] Libcodec2 loaded", path=file) break except OSError as err: - log.warning("[C2 ] Libcodec2 found but not loaded", path=file, e=err) + log.warning("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err) # Quit module if codec2 cant be loaded if api is None or "api" not in locals(): - log.critical("[C2 ] Libcodec2 not loaded - Exiting") + log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting") sys.exit(1) # ctypes function init diff --git a/tnc/config.py b/modem/config.py similarity index 97% rename from tnc/config.py rename to modem/config.py index 98a93fc1..032e7283 100644 --- a/tnc/config.py +++ b/modem/config.py @@ -45,7 +45,7 @@ class CONFIG: write entire config """ self.config['NETWORK'] = {'#Network settings': None, - 'TNCPORT': data[50] + 'ModemPORT': data[50] } self.config['STATION'] = {'#Station settings': None, @@ -66,7 +66,7 @@ class CONFIG: 'rigctld_ip': data[6], 'rigctld_port': data[7] } - self.config['TNC'] = {'#TNC settings': None, + self.config['Modem'] = {'#Modem settings': None, 'scatter': data[8], 'fft': data[9], 'narrowband': data[10], diff --git a/modem/cw.py b/modem/cw.py new file mode 100644 index 00000000..6a678050 --- /dev/null +++ b/modem/cw.py @@ -0,0 +1,70 @@ +import numpy as np + +""" + morse code generator + MorseCodePlayer().text_to_signal("DJ2LS-1") + + + """ +class MorseCodePlayer: + def __init__(self, wpm=150, f=1500, fs=48000): + self.wpm = wpm + self.f0 = f + self.fs = fs + self.dot_duration = 1.2/(self.wpm) + self.dash_duration = 3*self.dot_duration + self.pause_duration = self.dot_duration + self.word_pause_duration = 7*self.dot_duration + self.morse_alphabet = { + 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', + 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', + 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', + 'Y': '-.--', 'Z': '--..', '0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-', + '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.', '.': '.-.-.-', ',': '--..--', + '?': '..--..', "'": '.----.', '!': '-.-.--', '/': '-..-.', '(': '-.--.', ')': '-.--.-', '&': '.-...', + ':': '---...', ';': '-.-.-.', '=': '-...-', '+': '.-.-.', '-': '-....-', '_': '..--.-', '"': '.-..-.', + '$': '...-..-', '@': '.--.-.' + } + + def text_to_morse(self, text): + morse = '' + for char in text: + if char.upper() in self.morse_alphabet: + morse += self.morse_alphabet[char.upper()] + ' ' + elif char == ' ': + morse += ' ' + return morse + + def morse_to_signal(self, morse): + signal = np.array([], dtype=np.int16) + for char in morse: + if char == '.': + duration = int(self.dot_duration * self.fs) + s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) + signal = np.concatenate((signal, s * 32767)) + pause_duration = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + elif char == '-': + duration = int(self.dash_duration * self.fs) + s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) + signal = np.concatenate((signal, s * 32767)) + pause_duration = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + elif char == ' ': + pause_duration = int(self.word_pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + pause_duration = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + + pause_duration = int(self.word_pause_duration * self.fs) + #signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + + # Convert the signal to mono (single-channel) + #signal = signal.reshape(-1, 1) + + return signal + + def text_to_signal(self, text): + morse = self.text_to_morse(text) + return self.morse_to_signal(morse) + diff --git a/tnc/daemon.py b/modem/daemon.py similarity index 59% rename from tnc/daemon.py rename to modem/daemon.py index e7bb8c50..cec3eb06 100755 --- a/tnc/daemon.py +++ b/modem/daemon.py @@ -5,7 +5,7 @@ daemon.py Author: DJ2LS, January 2022 -daemon for providing basic information for the tnc like audio or serial devices +daemon for providing basic information for the modem like audio or serial devices """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member @@ -26,7 +26,7 @@ import crcengine import log_handler import serial.tools.list_ports import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog import ujson as json @@ -83,7 +83,7 @@ class DAEMON: """ while True: try: - if not Daemon.tncstarted: + if not Daemon.modemstarted: ( AudioParam.audio_input_devices, AudioParam.audio_output_devices, @@ -153,8 +153,8 @@ class DAEMON: # data[20] stats # data[21] tx_delay - if data[0] == "STARTTNC": - self.start_tnc(data) + if data[0] == "STARTModem": + self.start_modem(data) if data[0] == "TEST_HAMLIB": # data[9] radiocontrol @@ -162,6 +162,29 @@ class DAEMON: # data[11] rigctld_port self.test_hamlib_ptt(data) + if data[0] == "START_RIGCTLD": + """ + data[0] START_RIGCTLD, + data[1] hamlib_deviceid, + data[2] hamlib_deviceport, + data[3] hamlib_stop_bits, + data[4] hamlib_data_bits, + data[5] hamlib_handshake, + data[6] hamlib_serialspeed, + data[7] hamlib_dtrstate, + data[8] hamlib_pttprotocol, + data[9] hamlib_ptt_port, + data[10] hamlib_dcd, + data[11] hamlbib_serialspeed_ptt, + data[12] hamlib_rigctld_port, + data[13] hamlib_rigctld_ip, + data[14] hamlib_rigctld_path, + data[15] hamlib_rigctld_server_port, + data[16] hamlib_rigctld_custom_args + """ + self.start_rigctld(data) + + except Exception as err1: self.log.error("[DMN] worker: Exception: ", e=err1) @@ -214,8 +237,162 @@ class DAEMON: jsondata = json.dumps(response) sock.SOCKET_QUEUE.put(jsondata) - def start_tnc(self, data): - self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6]) + def start_rigctld(self, data): + # Seems to be working on Win + """ + data[0] START_RIGCTLD, + data[1] hamlib_deviceid, + data[2] hamlib_deviceport, + data[3] hamlib_stop_bits, + data[4] hamlib_data_bits, + data[5] hamlib_handshake, + data[6] hamlib_serialspeed, + data[7] hamlib_dtrstate, + data[8] hamlib_pttprotocol, + data[9] hamlib_ptt_port, + data[10] hamlib_dcd, + data[11] hamlbib_serialspeed_ptt, + data[12] hamlib_rigctld_port, + data[13] hamlib_rigctld_ip, + data[14] hamlib_rigctld_path, + data[15] hamlib_rigctld_server_port, + data[16] hamlib_rigctld_custom_args + """ + try: + command = [] + + isWin = False + + if sys.platform in ["darwin"]: + if data[14] not in [""]: + # hamlib_rigctld_path + application_path = data[14] + else: + application_path = "rigctld" + + command.append(f'{application_path}') + + elif sys.platform in ["linux", "darwin"]: + if data[14] not in [""]: + # hamlib_rigctld_path + application_path = data[14] + else: + application_path = "rigctld" + command.append(f'{application_path}') + elif sys.platform in ["win32", "win64"]: + isWin=True + if data[13].lower() == "localhost": + data[13]="127.0.0.1" + if data[14] not in [""]: + # hamlib_rigctld_path + application_path = data[14] + else: + application_path = "rigctld.exe" + command.append(f'{application_path}') + + + options = [] + + # hamlib_deviceid + if data[1] not in [None, "None", "ignore"]: + options.append(("--model=" + data[1] )) + + # hamlib_deviceport + if data[2] not in [None, "None", "ignore"]: + options.append(("--rig-file="+ data[2])) + # hamlib_stop_bits + if data[3] not in [None, "None", "ignore"]: + options.append(("--set-conf=stop_bits=" + data[3])) + + # hamlib_data_bits + if data[4] not in [None, "None", "ignore"]: + options.append(("--set-conf=data_bits=" + data[4])) + + + # hamlib_handshake + if data[5] not in [None, "None", "ignore"]: + options.append(("--set-conf=serial_handshake=" + data[5])) + + + # hamlib_serialspeed + if data[6] not in [None, "None", "ignore"]: + options.append(("--serial-speed=" + data[6])) + + # hamlib_dtrstate + if data[7] not in [None, "None", "ignore"]: + options.append(("--set-conf=dtr_state=" + data[7])) + + + # hamlib_pttprotocol + if data[8] not in [None, "None", "ignore"]: + options.append(("--ptt-type=" + data[8])) + + + # hamlib_ptt_port + if data[9] not in [None, "None", "ignore"]: + options.append(("--ptt-file=" + data[9])) + + + # hamlib_dcd + if data[10] not in [None, "None", "ignore"]: + options.append(("--dcd-type=" + data[10])) + + + # hamlbib_serialspeed_ptt + if data[11] not in [None, "None", "ignore"]: + # options.extend(("-m", data[11])) + pass + + # hamlib_rigctld_port + # Using this ensures rigctld starts on port configured in GUI + if data[12] not in [None, "None", "ignore"]: + options.append(("--port="+ data[12])) + + # hamlib_rigctld_ip + if data[13] not in [None, "None", "ignore"]: + options.append(("--listen-addr="+ data[13])) + + # data[14] == hamlib_rigctld_path + # maybe at wrong place in list... + #Not needed for setting command line arguments + + # hamlib_rigctld_server_port + # Ignore configured value and use value configured in GUI + #if data[15] not in [None, "None", "ignore"]: + # options.extend(("-m", data[15])) + # pass + + # hamlib_rigctld_custom_args + if data[16] not in [None, "None", "ignore"]: + for o in data[16].split(" "): + options.append(o) + + # append debugging paramter + options.append(("-vvv")) + command += options + + self.log.info("[DMN] starting rigctld: ", param=command) + + if not isWin: + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + else: + #On windows, open rigctld in new window for easier troubleshooting + proc = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_CONSOLE,close_fds=True) + + atexit.register(proc.kill) + + Daemon.rigctldstarted = True + Daemon.rigctldprocess = proc + + + + except Exception as err: + self.log.warning("[DMN] err starting rigctld: ", e=err) + + + + def start_modem(self, data): + self.log.warning("[DMN] Starting Modem", rig=data[5], port=data[6]) # list of parameters, necessary for running subprocess command as a list options = ["--port", str(DAEMON.port - 1)] @@ -294,8 +471,8 @@ class DAEMON: # safe data to config file config.write_entire_config(data) - # Try running tnc from binary, else run from source - # This helps running the tnc in a developer environment + # Try running modem from binary, else run from source + # This helps running the modem in a developer environment try: command = [] @@ -304,12 +481,12 @@ class DAEMON: # extends the sys module by a flag frozen=True and sets the app # path into variable _MEIPASS'. application_path = sys._MEIPASS - command.append(f'{application_path}/freedata-tnc') + command.append(f'{application_path}/freedata-modem') elif sys.platform in ["linux", "darwin"]: - command.append("./freedata-tnc") + command.append("./freedata-modem") elif sys.platform in ["win32", "win64"]: - command.append("freedata-tnc.exe") + command.append("freedata-modem.exe") command += options @@ -317,25 +494,37 @@ class DAEMON: atexit.register(proc.kill) - self.log.info("[DMN] TNC started", path="binary") + Daemon.modemprocess = proc + Daemon.modemstarted = True + + + self.log.info("[DMN] Modem started", path="binary") 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"]: - command.append("python") + try: + self.log.info("[DMN] worker: ", e=err1) + command = [] + + if sys.platform in ["linux", "darwin"]: + command.append("python3") + elif sys.platform in ["win32", "win64"]: + command.append("python") + + command.append("main.py") + command += options + proc = subprocess.Popen(command) + atexit.register(proc.kill) + + self.log.info("[DMN] Modem started", path="source") + + Daemon.modemprocess = proc + Daemon.modemstarted = True + except Exception as e: + self.log.error("[DMN] Modem not started", error=e) + Daemon.modemstarted = False - command.append("main.py") - command += options - proc = subprocess.Popen(command) - atexit.register(proc.kill) - self.log.info("[DMN] TNC started", path="source") - Daemon.tncprocess = proc - Daemon.tncstarted = True if __name__ == "__main__": mainlog = structlog.get_logger(__file__) # we need to run this on Windows for multiprocessing support @@ -384,7 +573,7 @@ if __name__ == "__main__": # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True cmdserver = sock.ThreadedTCPServer( - (TNC.host, DAEMON.port), sock.ThreadedTCPRequestHandler + (Modem.host, DAEMON.port), sock.ThreadedTCPRequestHandler ) server_thread = threading.Thread(target=cmdserver.serve_forever) server_thread.daemon = True @@ -401,7 +590,7 @@ if __name__ == "__main__": "[DMN] Starting FreeDATA Daemon", author="DJ2LS", year="2023", - version=TNC.version, + version=Modem.version, ) while True: threading.Event().wait(1) diff --git a/tnc/data_handler.py b/modem/data_handler.py similarity index 88% rename from tnc/data_handler.py rename to modem/data_handler.py index f1afa51a..6ed9d3cb 100644 --- a/tnc/data_handler.py +++ b/modem/data_handler.py @@ -24,7 +24,7 @@ import helpers import modem import numpy as np import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog import stats import ujson as json @@ -138,14 +138,14 @@ class DATA: self.snr_list_high_bw = [-100, 0, 3] # List for time to wait for corresponding mode in seconds # test with 6,7 --> caused sometimes a frame timeout if ack frame takes longer - # TODO: Need to check why ACK frames needs more time - # TODO: Adjust these times + # TODO Need to check why ACK frames needs more time + # TODO Adjust these times self.time_list_high_bw = [6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1] # -------------- AVAILABLE MODES END----------- # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth # but ability to fall back to low bandwidth modes if needed. - if TNC.low_bandwidth_mode: + if Modem.low_bandwidth_mode: # List of codec2 modes to use in "low bandwidth" mode. self.mode_list = self.mode_list_low_bw # list of times to wait for corresponding mode in seconds @@ -280,12 +280,12 @@ class DATA: # send transmission queued information once if ARQ.arq_state or ModemParam.is_codec2_traffic: self.log.debug( - "[TNC] TX DISPATCHER - waiting with processing command ", + "[Modem] TX DISPATCHER - waiting with processing command ", arq_state=ARQ.arq_state, ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", command=data[0], status="queued", ) @@ -299,7 +299,7 @@ class DATA: # Dispatch commands known to command_dispatcher if data[0] in self.command_dispatcher: - self.log.debug(f"[TNC] TX {self.command_dispatcher[data[0]][1]}...") + self.log.debug(f"[Modem] TX {self.command_dispatcher[data[0]][1]}...") self.command_dispatcher[data[0]][0]() # Dispatch commands that need more arguments. @@ -346,7 +346,7 @@ class DATA: self.send_fec(data[1], data[2], data[3], data[4]) else: self.log.error( - "[TNC] worker_transmit: received invalid command:", data=data + "[Modem] worker_transmit: received invalid command:", data=data ) def worker_receive(self) -> None: @@ -373,7 +373,7 @@ class DATA: """ self.log.debug( - "[TNC] process_data:", n_retries_per_burst=self.n_retries_per_burst + "[Modem] process_data:", n_retries_per_burst=self.n_retries_per_burst ) # Process data only if broadcast or we are the receiver @@ -415,16 +415,16 @@ class DATA: # Dispatch activity based on received frametype if frametype in self.rx_dispatcher: # Process frames "known" by rx_dispatcher - # self.log.debug(f"[TNC] {self.rx_dispatcher[frametype][1]} RECEIVED....") + # self.log.debug(f"[Modem] {self.rx_dispatcher[frametype][1]} RECEIVED....") self.rx_dispatcher[frametype][0](bytes_out[:-2]) # Process frametypes requiring a different set of arguments. elif FR_TYPE.BURST_51.value >= frametype >= FR_TYPE.BURST_01.value: # get snr of received data - # FIXME: find a fix for this - after moving to classes, this no longer works + # FIXME find a fix for this - after moving to classes, this no longer works # snr = self.calculate_snr(freedv) snr = ModemParam.snr - self.log.debug("[TNC] RX SNR", snr=snr) + self.log.debug("[Modem] RX SNR", snr=snr) # send payload data to arq checker without CRC16 self.arq_data_received( bytes(bytes_out[:-2]), bytes_per_frame, snr, freedv @@ -432,23 +432,23 @@ class DATA: # if we received the last frame of a burst or the last remaining rpt frame, do a modem unsync # if ARQ.rx_burst_buffer.count(None) <= 1 or (frame+1) == n_frames_per_burst: - # self.log.debug(f"[TNC] LAST FRAME OF BURST --> UNSYNC {frame+1}/{n_frames_per_burst}") + # self.log.debug(f"[Modem] LAST FRAME OF BURST --> UNSYNC {frame+1}/{n_frames_per_burst}") # self.c_lib.freedv_set_sync(freedv, 0) # TESTFRAMES elif frametype == FR_TYPE.TEST_FRAME.value: - self.log.debug("[TNC] TESTFRAME RECEIVED", frame=bytes_out[:]) + self.log.debug("[Modem] TESTFRAME RECEIVED", frame=bytes_out[:]) # Unknown frame type else: self.log.warning( - "[TNC] ARQ - other frame type", frametype=FR_TYPE(frametype).name + "[Modem] ARQ - other frame type", frametype=FR_TYPE(frametype).name ) else: # for debugging purposes to receive all data self.log.debug( - "[TNC] Foreign frame received", + "[Modem] Foreign frame received", frame=bytes_out[:-2].hex(), frame_type=FR_TYPE(int.from_bytes(bytes_out[:1], byteorder="big")).name, ) @@ -461,7 +461,7 @@ class DATA: repeat_delay=0, ) -> None: """ - Send (transmit) supplied frame to TNC + Send (transmit) supplied frame to Modem :param frame_to_tx: Frame data to send :type frame_to_tx: list of bytearrays @@ -475,16 +475,16 @@ class DATA: #print(frame_to_tx[0]) #print(frame_to_tx) frame_type = FR_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name - self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, + self.log.debug("[Modem] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, type=frame_type) # Set the TRANSMITTING flag before adding an object to the transmit queue - # TODO: This is not that nice, we could improve this somehow - TNC.transmitting = True + # TODO This is not that nice, we could improve this somehow + Modem.transmitting = True modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx]) # Wait while transmitting - while TNC.transmitting: + while Modem.transmitting: threading.Event().wait(0.01) def send_data_to_socket_queue(self, **jsondata): @@ -495,7 +495,7 @@ class DATA: Dictionary containing the data to be sent, in the format: key=value, for each item. E.g.: self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="received", status="success", uuid=self.transmission_uuid, @@ -515,12 +515,12 @@ class DATA: if "dxcallsign" not in jsondata: jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8") except Exception as e: - self.log.debug("[TNC] error adding callsigns to network message", e=e) + self.log.debug("[Modem] error adding callsigns to network message", e=e) # run json dumps json_data_out = json.dumps(jsondata) - self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=json_data_out) + self.log.debug("[Modem] send_data_to_socket_queue:", jsondata=json_data_out) # finally push data to our network queue sock.SOCKET_QUEUE.put(json_data_out) @@ -570,13 +570,13 @@ class DATA: # reset burst timeout in case we had to wait too long self.burst_last_received = time.time() + channel_busy_timeout + 8 # Transmit frame - # TODO: Do we have to send , self.send_ident_frame(False) ? + # TODO Do we have to send , self.send_ident_frame(False) ? # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) def send_retransmit_request_frame(self) -> None: # check where a None is in our burst buffer and do frame+1, because lists start at 0 - # FIXME: Check to see if there's a `frame - 1` in the receive portion. Remove both if there is. + # FIXME Check to see if there's a `frame - 1` in the receive portion. Remove both if there is. #print(ARQ.rx_burst_buffer) missing_frames = [ frame + 1 @@ -589,7 +589,7 @@ class DATA: rpt_frame[1:2] = self.session_id rpt_frame[2:2 + len(missing_frames)] = missing_frames - self.log.info("[TNC] ARQ | RX | Requesting", frames=missing_frames) + self.log.info("[Modem] ARQ | RX | Requesting", frames=missing_frames) # Transmit frame self.enqueue_frame_for_tx([rpt_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) @@ -604,7 +604,7 @@ class DATA: nack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") # TRANSMIT NACK FRAME FOR BURST - # TODO: Do we have to send ident frame? + # TODO Do we have to send ident frame? # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) # wait while timeout not reached and our busy state is busy @@ -627,7 +627,7 @@ class DATA: ARQ.rx_burst_buffer = [] # Create and send ACK frame - self.log.info("[TNC] ARQ | RX | SENDING NACK") + self.log.info("[Modem] ARQ | RX | SENDING NACK") nack_frame = bytearray(self.length_sig1_frame) nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value]) nack_frame[1:2] = self.session_id @@ -654,11 +654,11 @@ class DATA: disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value]) disconnection_frame[1:2] = self.session_id disconnection_frame[2:5] = Station.dxcallsign_crc - # TODO: Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) + # TODO Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) # self.enqueue_frame_for_tx([disconnection_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig0.value, copies=5, repeat_delay=0) - # TODO: We need to add the ident frame feature with a seperate PR after publishing latest protocol - # TODO: We need to wait some time between last arq related signalling frame and ident frame - # TODO: Maybe about 500ms - 1500ms to avoid confusion and too much PTT toggles + # TODO We need to add the ident frame feature with a seperate PR after publishing latest protocol + # TODO We need to wait some time between last arq related signalling frame and ident frame + # TODO Maybe about 500ms - 1500ms to avoid confusion and too much PTT toggles # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 5 @@ -684,14 +684,14 @@ class DATA: data_in = bytes(data_in) # only process data if we are in ARQ and BUSY state else return to quit - if not ARQ.arq_state and TNC.tnc_state not in ["BUSY"]: - self.log.warning("[TNC] wrong tnc state - dropping data", arq_state=ARQ.arq_state, - tnc_state=TNC.tnc_state) + if not ARQ.arq_state and Modem.modem_state not in ["BUSY"]: + self.log.warning("[Modem] wrong modem state - dropping data", arq_state=ARQ.arq_state, + modem_state=Modem.modem_state) return self.arq_file_transfer = True - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" ARQ.arq_state = True # Update data_channel timestamp @@ -717,7 +717,7 @@ class DATA: helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, @@ -736,34 +736,34 @@ class DATA: # free up burst buffer ARQ.rx_burst_buffer = [] - # TODO: Needs to be removed as soon as mode error is fixed + # TODO Needs to be removed as soon as mode error is fixed # catch possible modem error which leads into false byteorder # modem possibly decodes too late - data then is pushed to buffer # which leads into wrong byteorder - # Lets put this in try/except so we are not crashing tnc as its highly experimental + # Lets put this in try/except so we are not crashing modem as its highly experimental # This might only work for datac1 and datac3 try: # area_of_interest = (modem.get_bytes_per_frame(self.mode_list[speed_level] - 1) -3) * 2 if ARQ.rx_frame_buffer.endswith(temp_burst_buffer[:246]) and len(temp_burst_buffer) >= 246: self.log.warning( - "[TNC] ARQ | RX | wrong byteorder received - dropping data" + "[Modem] ARQ | RX | wrong byteorder received - dropping data" ) # we need to run a return here, so we are not sending an ACK # return except Exception as e: self.log.warning( - "[TNC] ARQ | RX | wrong byteorder check failed", e=e + "[Modem] ARQ | RX | wrong byteorder check failed", e=e ) - self.log.debug("[TNC] temp_burst_buffer", buffer=temp_burst_buffer) - self.log.debug("[TNC] ARQ.rx_frame_buffer", buffer=ARQ.rx_frame_buffer) + self.log.debug("[Modem] temp_burst_buffer", buffer=temp_burst_buffer) + self.log.debug("[Modem] ARQ.rx_frame_buffer", buffer=ARQ.rx_frame_buffer) # if frame buffer ends not with the current frame, we are going to append new data # if data already exists, we received the frame correctly, # but the ACK frame didn't receive its destination (ISS) if ARQ.rx_frame_buffer.endswith(temp_burst_buffer): self.log.info( - "[TNC] ARQ | RX | Frame already received - sending ACK again" + "[Modem] ARQ | RX | Frame already received - sending ACK again" ) else: @@ -795,12 +795,12 @@ class DATA: : search_position + get_position ] self.log.warning( - "[TNC] ARQ | RX | replacing existing buffer data", + "[Modem] ARQ | RX | replacing existing buffer data", area=search_area, pos=get_position, ) else: - self.log.debug("[TNC] ARQ | RX | appending data to buffer") + self.log.debug("[Modem] ARQ | RX | appending data to buffer") ARQ.rx_frame_buffer += temp_burst_buffer @@ -818,7 +818,7 @@ class DATA: self.data_channel_last_received = int(time.time()) + 6 + 6 self.burst_last_received = int(time.time()) + 6 + 6 # Create and send ACK frame - self.log.info("[TNC] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish, + self.log.info("[Modem] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish, bytesperminute=ARQ.bytes_per_minute) self.send_burst_ack_frame(snr) @@ -833,7 +833,7 @@ class DATA: # send a network message with information self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="receiving", uuid=self.transmission_uuid, @@ -851,9 +851,9 @@ class DATA: # # Check if we received last frame of burst - this is an indicator for missed frames. # # With this way of doing this, we always MUST receive the last # # frame of a burst otherwise the entire burst is lost - # # TODO: See if a timeout on the send side with re-transmit last burst would help. + # # TODO See if a timeout on the send side with re-transmit last burst would help. # self.log.debug( - # "[TNC] last frames of burst received:", + # "[Modem] last frames of burst received:", # frame=self.rx_n_frame_of_burst, # frames=self.rx_n_frames_per_burst, # @@ -864,20 +864,20 @@ class DATA: # elif self.rx_n_frame_of_burst not in [self.rx_n_frames_per_burst - 1]: # self.log.info( - # "[TNC] data_handler: received burst", + # "[Modem] data_handler: received burst", # frame=self.rx_n_frame_of_burst + 1, # frames=self.rx_n_frames_per_burst, # ) # else: # self.log.error( - # "[TNC] data_handler: Should not reach this point...", + # "[Modem] data_handler: Should not reach this point...", # frame=self.rx_n_frame_of_burst + 1, # frames=self.rx_n_frames_per_burst, # ) else: self.log.warning( - "[TNC] data_handler: missing data in burst buffer...", + "[Modem] data_handler: missing data in burst buffer...", frame=self.rx_n_frame_of_burst + 1, frames=self.rx_n_frames_per_burst ) @@ -898,7 +898,7 @@ class DATA: and None not in ARQ.rx_burst_buffer ): self.log.debug( - "[TNC] arq_data_received:", + "[Modem] arq_data_received:", bof_position=bof_position, eof_position=eof_position, ) @@ -920,9 +920,9 @@ class DATA: data_frame_crc_received = helpers.get_crc_32(data_frame) # check if hmac signing enabled - if TNC.enable_hmac: + if Modem.enable_hmac: self.log.info( - "[TNC] [HMAC] Enabled", + "[Modem] [HMAC] Enabled", ) # now check if we have valid hmac signature - returns salt or bool salt_found = helpers.search_hmac_salt(self.dxcallsign, self.mycallsign, data_frame_crc, data_frame, token_iters=100) @@ -937,12 +937,12 @@ class DATA: self.arq_process_received_data_frame(data_frame, snr, signed=False) elif data_frame_crc == data_frame_crc_received: self.log.warning( - "[TNC] [HMAC] Disabled, using CRC", + "[Modem] [HMAC] Disabled, using CRC", ) self.arq_process_received_data_frame(data_frame, snr, signed=False) else: self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", uuid=self.transmission_uuid, @@ -953,7 +953,7 @@ class DATA: duration = time.time() - self.rx_start_of_transmission self.log.warning( - "[TNC] ARQ | RX | DATA FRAME NOT SUCCESSFULLY RECEIVED!", + "[Modem] ARQ | RX | DATA FRAME NOT SUCCESSFULLY RECEIVED!", e="wrong crc", expected=data_frame_crc.hex(), received=data_frame_crc_received.hex(), @@ -965,10 +965,10 @@ class DATA: data=data_frame, ) - if TNC.enable_stats: + if Modem.enable_stats: self.stats.push(frame_nack_counter=self.frame_nack_counter, status="wrong_crc", duration=duration) - self.log.info("[TNC] ARQ | RX | Sending NACK", finished=ARQ.arq_seconds_until_finish, + self.log.info("[Modem] ARQ | RX | Sending NACK", finished=ARQ.arq_seconds_until_finish, bytesperminute=ARQ.bytes_per_minute) self.send_burst_nack_frame(snr) @@ -1003,7 +1003,7 @@ class DATA: mode_slots = FREEDV_MODE_USED_SLOTS[mode_name].value if mode_slots in [ModemParam.channel_busy_slot]: self.log.warning( - "[TNC] busy slot detection", + "[Modem] busy slot detection", slots=ModemParam.channel_busy_slot, mode_slots=mode_slots, ) @@ -1027,7 +1027,7 @@ class DATA: else: - self.log.info("[TNC] ARQ | increasing speed level not possible because of SNR limit", + self.log.info("[Modem] ARQ | increasing speed level not possible because of SNR limit", given_snr=ModemParam.snr, needed_snr=self.snr_list[new_speed_level] ) @@ -1054,7 +1054,7 @@ class DATA: self.calculate_transfer_rate_rx( self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) ) - self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, + self.log.info("[Modem] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, bytesperminute=ARQ.bytes_per_minute, total_bytes=ARQ.total_bytes, duration=duration, hmac_signed=signed) # Decompress the data frame @@ -1077,7 +1077,7 @@ class DATA: else: # if full, free space by getting an item self.log.info( - "[TNC] ARQ | RX | RX_BUFFER FULL - dropping old data", + "[Modem] ARQ | RX | RX_BUFFER FULL - dropping old data", buffer_size=RX_BUFFER.qsize(), maxsize=int(ARQ.rx_buffer_size) ) @@ -1085,11 +1085,24 @@ class DATA: # add item to RX_BUFFER self.log.info( - "[TNC] ARQ | RX | saving data to rx buffer", + "[Modem] ARQ | RX | saving data to rx buffer", buffer_size=RX_BUFFER.qsize() + 1, maxsize=RX_BUFFER.maxsize ) try: + # RX_BUFFER[0] = transmission uuid + # RX_BUFFER[1] = timestamp + # RX_BUFFER[2] = dxcallsign + # RX_BUFFER[3] = dxgrid + # RX_BUFFER[4] = data + # RX_BUFFER[5] = hmac signed + # RX_BUFFER[6] = compression factor + # RX_BUFFER[7] = bytes per minute + # RX_BUFFER[8] = duration + # RX_BUFFER[9] = self.frame_nack_counter + # RX_BUFFER[10] = speed list stats + + RX_BUFFER.put( [ self.transmission_uuid, @@ -1097,6 +1110,12 @@ class DATA: Station.dxcallsign, Station.dxgrid, base64_data, + signed, + ARQ.arq_compression_factor, + ARQ.bytes_per_minute, + duration, + self.frame_nack_counter, + ARQ.speed_list ] ) except Exception as e: @@ -1106,7 +1125,7 @@ class DATA: # # Occurs on Raspberry Pi and Python 3.7 self.log.error( - "[TNC] ARQ | RX | error occurred when saving data!", + "[Modem] ARQ | RX | error occurred when saving data!", e=e, uuid=self.transmission_uuid, timestamp=timestamp, @@ -1127,7 +1146,7 @@ class DATA: ) except Exception as e: self.log.error( - "[TNC] ARQ | RX | can't save file to folder", + "[Modem] ARQ | RX | can't save file to folder", e=e, uuid=self.transmission_uuid, timestamp=timestamp, @@ -1137,7 +1156,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="received", uuid=self.transmission_uuid, @@ -1151,15 +1170,18 @@ class DATA: dxgrid=str(Station.dxgrid, "UTF-8"), data=base64_data, irs=helpers.bool_to_string(self.is_IRS), - hmac_signed=signed + hmac_signed=signed, + duration=duration, + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) - if TNC.enable_stats: + if Modem.enable_stats: duration = time.time() - self.rx_start_of_transmission self.stats.push(frame_nack_counter=self.frame_nack_counter, status="received", duration=duration) self.log.info( - "[TNC] ARQ | RX | SENDING DATA FRAME ACK") + "[Modem] ARQ | RX | SENDING DATA FRAME ACK") self.send_data_ack_frame(snr) # Update statistics AFTER the frame ACK is sent @@ -1168,7 +1190,7 @@ class DATA: ) self.log.info( - "[TNC] | RX | DATACHANNEL [" + "[Modem] | RX | DATACHANNEL [" + str(self.mycallsign, "UTF-8") + "]<< >>[" + str(Station.dxcallsign, "UTF-8") @@ -1206,7 +1228,7 @@ class DATA: compression_factor = bytes([int(ARQ.arq_compression_factor * 10)]) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="transmitting", uuid=self.transmission_uuid, @@ -1220,7 +1242,7 @@ class DATA: ) self.log.info( - "[TNC] | TX | DATACHANNEL", + "[Modem] | TX | DATACHANNEL", Bytes=ARQ.total_bytes, ) @@ -1237,12 +1259,12 @@ class DATA: hmac_digest = hmac.new(hmac_salt, data_out, hashlib.sha256).digest() # truncate to 32bit frame_payload_crc = hmac_digest[:4] - self.log.debug("[TNC] frame payload HMAC:", crc=frame_payload_crc.hex()) + self.log.debug("[Modem] frame payload HMAC:", crc=frame_payload_crc.hex()) else: # Append a crc at the beginning and end of file indicators frame_payload_crc = helpers.get_crc_32(data_out) - self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc.hex()) + self.log.debug("[Modem] frame payload CRC:", crc=frame_payload_crc.hex()) # Assemble the data frame data_out = ( @@ -1253,7 +1275,7 @@ class DATA: + data_out + self.data_frame_eof ) - self.log.debug("[TNC] frame raw data:", data=data_out) + self.log.debug("[Modem] frame raw data:", data=data_out) # Initial bufferposition is 0 bufferposition = 0 bufferposition_end = 0 @@ -1273,7 +1295,7 @@ class DATA: data_mode = self.mode_list[self.speed_level] self.log.debug( - "[TNC] Speed-level:", + "[Modem] Speed-level:", level=self.speed_level, retry=self.tx_n_retry_of_burst, mode=FREEDV_MODE(data_mode).name, @@ -1282,7 +1304,7 @@ class DATA: # Payload information payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2 - self.log.info("[TNC] early buffer info", + self.log.info("[Modem] early buffer info", bufferposition=bufferposition, bufferposition_end=bufferposition_end, bufferposition_burst_start=bufferposition_burst_start @@ -1300,7 +1322,7 @@ class DATA: break else: n_frames_per_burst = 1 - self.log.info("[TNC] calculated frames_per_burst:", n=n_frames_per_burst) + self.log.info("[Modem] calculated frames_per_burst:", n=n_frames_per_burst) tempbuffer = [] self.rpt_request_buffer = [] @@ -1313,14 +1335,14 @@ class DATA: arqheader[2:3] = self.session_id # only check for buffer position if at least one NACK received - self.log.info("[TNC] ----- data buffer position:", iss_buffer_pos=bufferposition, + self.log.info("[Modem] ----- data buffer position:", iss_buffer_pos=bufferposition, irs_bufferposition=self.irs_buffer_position) if self.frame_nack_counter > 0 and self.irs_buffer_position != bufferposition: - self.log.error("[TNC] ----- data buffer offset:", iss_buffer_pos=bufferposition, + self.log.error("[Modem] ----- data buffer offset:", iss_buffer_pos=bufferposition, irs_bufferposition=self.irs_buffer_position) # only adjust buffer position for experimental versions - if 'exp' in TNC.version: - self.log.warning("[TNC] ----- data adjustment disabled!") + if 'exp' in Modem.version: + self.log.warning("[Modem] ----- data adjustment disabled!") # bufferposition = self.irs_buffer_position bufferposition_end = bufferposition + payload_per_frame - len(arqheader) @@ -1345,9 +1367,9 @@ class DATA: # set new buffer position bufferposition = bufferposition_end - self.log.debug("[TNC] tempbuffer:", tempbuffer=tempbuffer) + self.log.debug("[Modem] tempbuffer:", tempbuffer=tempbuffer) self.log.info( - "[TNC] ARQ | TX | FRAMES", + "[Modem] ARQ | TX | FRAMES", mode=FREEDV_MODE(data_mode).name, fpb=n_frames_per_burst, retry=self.tx_n_retry_of_burst, @@ -1370,7 +1392,7 @@ class DATA: self.burst_ack = False # reset ack state self.tx_n_retry_of_burst = 0 # reset retries self.log.debug( - "[TNC] arq_transmit: Received BURST ACK. Sending next chunk." + "[Modem] arq_transmit: Received BURST ACK. Sending next chunk." , irs_snr=self.burst_ack_snr) # update temp bufferposition for n frames per burst early calculation bufferposition_burst_start = bufferposition_end @@ -1378,7 +1400,7 @@ class DATA: if self.data_frame_ack_received: self.log.debug( - "[TNC] arq_transmit: Received FRAME ACK. Braking retry loop." + "[Modem] arq_transmit: Received FRAME ACK. Braking retry loop." ) break # break retry loop @@ -1386,7 +1408,7 @@ class DATA: self.tx_n_retry_of_burst += 1 self.log.warning( - "[TNC] arq_transmit: Received BURST NACK. Resending data", + "[Modem] arq_transmit: Received BURST NACK. Resending data", bufferposition_burst_start=bufferposition_burst_start, bufferposition=bufferposition ) @@ -1400,7 +1422,7 @@ class DATA: # ARQ.arq_state == "DATA" --> when stopping transmission manually if not ARQ.arq_state: self.log.debug( - "[TNC] arq_transmit: ARQ State changed to FALSE. Breaking retry loop." + "[Modem] arq_transmit: ARQ State changed to FALSE. Breaking retry loop." ) break @@ -1409,7 +1431,7 @@ class DATA: ) # NEXT ATTEMPT self.log.debug( - "[TNC] ATTEMPT:", + "[Modem] ATTEMPT:", retry=self.tx_n_retry_of_burst, maxretries=self.tx_n_max_retries_per_burst, overflows=AudioParam.buffer_overflow_counter, @@ -1424,7 +1446,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="transmitting", uuid=self.transmission_uuid, @@ -1442,7 +1464,7 @@ class DATA: # the loop exits after sending the last frame only once and doesn't # wait for an acknowledgement. if self.data_frame_ack_received and bufferposition > len(data_out): - self.log.debug("[TNC] arq_tx: Last fragment sent and acknowledged.") + self.log.debug("[Modem] arq_tx: Last fragment sent and acknowledged.") break # GOING TO NEXT ITERATION @@ -1453,7 +1475,7 @@ class DATA: if TESTMODE: # Quit after transmission - self.log.debug("[TNC] TESTMODE: arq_transmit exiting.") + self.log.debug("[Modem] TESTMODE: arq_transmit exiting.") sys.exit(0) def arq_transmit_success(self): @@ -1466,7 +1488,7 @@ class DATA: # so let's sleep a little threading.Event().wait(0.2) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="transmitted", uuid=self.transmission_uuid, @@ -1476,11 +1498,13 @@ class DATA: finished=ARQ.arq_seconds_until_finish, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) self.log.info( - "[TNC] ARQ | TX | DATA TRANSMITTED!", + "[Modem] ARQ | TX | DATA TRANSMITTED!", BytesPerMinute=ARQ.bytes_per_minute, total_bytes=ARQ.total_bytes, BitsPerSecond=ARQ.arq_bits_per_second, @@ -1496,7 +1520,7 @@ class DATA: will be called if we not successfully transmitted all of queued data """ self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", uuid=self.transmission_uuid, @@ -1505,11 +1529,13 @@ class DATA: compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) self.log.info( - "[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", + "[Modem] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", overflows=AudioParam.buffer_overflow_counter, ) @@ -1532,7 +1558,7 @@ class DATA: helpers.add_to_heard_stations( self.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, @@ -1542,7 +1568,7 @@ class DATA: if frametype == FR_TYPE.BURST_ACK.value: # Increase speed level if we received a burst ack # self.speed_level = min(self.speed_level + 1, len(self.mode_list) - 1) - # Force data retry loops of TX TNC to stop and continue with next frame + # Force data retry loops of TX Modem to stop and continue with next frame self.burst_ack = True # Reset burst nack counter self.burst_nack_counter = 0 @@ -1563,7 +1589,7 @@ class DATA: self.irs_buffer_position = int.from_bytes(data_in[5:9], "big") self.log.warning( - "[TNC] ARQ | TX | Burst NACK received", + "[Modem] ARQ | TX | Burst NACK received", burst_nack_counter=self.burst_nack_counter, irs_buffer_position=self.irs_buffer_position, ) @@ -1587,12 +1613,12 @@ class DATA: helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) - # Force data loops of TNC to stop and continue with next frame + # Force data loops of Modem to stop and continue with next frame self.data_frame_ack_received = True # Update arq_session and data_channel timestamp self.data_channel_last_received = int(time.time()) @@ -1608,7 +1634,7 @@ class DATA: data_in:bytes: """ - self.log.warning("[TNC] ARQ FRAME NACK RECEIVED - cleanup!", + self.log.warning("[Modem] ARQ FRAME NACK RECEIVED - cleanup!", arq="transmission", status="failed", uuid=self.transmission_uuid, @@ -1616,20 +1642,22 @@ class DATA: bytesperminute=ARQ.bytes_per_minute, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) Station.dxgrid = b'------' helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", uuid=self.transmission_uuid, @@ -1638,7 +1666,9 @@ class DATA: compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) # Update data_channel timestamp self.arq_session_last_received = int(time.time()) @@ -1654,19 +1684,19 @@ class DATA: """ # Only process data if we are in ARQ and BUSY state - if not ARQ.arq_state or TNC.tnc_state != "BUSY": + if not ARQ.arq_state or Modem.modem_state != "BUSY": return Station.dxgrid = b'------' helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) - self.log.info("[TNC] ARQ REPEAT RECEIVED") + self.log.info("[Modem] ARQ REPEAT RECEIVED") # self.rpt_request_received = True # Update data_channel timestamp @@ -1684,7 +1714,7 @@ class DATA: missing_frames_buffer_position = missing_area[i] - 1 tempbuffer_rptframes.append(self.rpt_request_buffer[missing_frames_buffer_position]) - self.log.info("[TNC] SENDING REPEAT....") + self.log.info("[Modem] SENDING REPEAT....") data_mode = self.mode_list[self.speed_level] self.enqueue_frame_for_tx(tempbuffer_rptframes, c2_mode=data_mode) @@ -1712,10 +1742,10 @@ class DATA: Station.dxcallsign = self.dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(self.dxcallsign) - # TODO: we need to check this, maybe placing it to class init + # TODO we need to check this, maybe placing it to class init self.datachannel_timeout = False self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") + "]>> <<[" + str(self.dxcallsign, "UTF-8") @@ -1725,9 +1755,9 @@ class DATA: # Let's check if we have a busy channel if ModemParam.channel_busy: - self.log.warning("[TNC] Channel busy, waiting until free...") + self.log.warning("[Modem] Channel busy, waiting until free...") self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="waiting", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1741,10 +1771,10 @@ class DATA: # if channel busy timeout reached stop connecting if time.time() > channel_busy_timeout: - self.log.warning("[TNC] Channel busy, try again later...") + self.log.warning("[Modem] Channel busy, try again later...") ARQ.arq_session_state = "failed" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="failed", reason="busy", @@ -1760,7 +1790,7 @@ class DATA: threading.Event().wait(0.01) ARQ.arq_session_state = "connecting" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connecting", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1769,7 +1799,7 @@ class DATA: if ARQ.arq_session and ARQ.arq_session_state == "connected": # ARQ.arq_session_state = "connected" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connected", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1778,7 +1808,7 @@ class DATA: return True self.log.warning( - "[TNC] SESSION FAILED [" + "[Modem] SESSION FAILED [" + str(self.mycallsign, "UTF-8") + "]>>X<<[" + str(self.dxcallsign, "UTF-8") @@ -1789,7 +1819,7 @@ class DATA: ) ARQ.arq_session_state = "failed" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="failed", reason="timeout", @@ -1823,7 +1853,7 @@ class DATA: threading.Event().wait(0.01) for attempt in range(self.session_connect_max_retries): self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") + "]>>?<<[" + str(self.dxcallsign, "UTF-8") @@ -1833,7 +1863,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connecting", attempt=attempt + 1, @@ -1868,7 +1898,7 @@ class DATA: # Given the while condition, it will only exit when `ARQ.arq_session` is True self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connected", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1884,7 +1914,7 @@ class DATA: data_in:bytes: """ # if we don't want to respond to calls, return False - if not TNC.respond_to_call: + if not Modem.respond_to_call: return False # ignore channel opener if already in ARQ STATE @@ -1913,13 +1943,13 @@ class DATA: helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") + "]>>|<<[" + str(self.dxcallsign, "UTF-8") @@ -1927,10 +1957,10 @@ class DATA: state=ARQ.arq_session_state, ) ARQ.arq_session = True - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connected", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1943,7 +1973,7 @@ class DATA: ARQ.arq_session_state = "disconnecting" self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") + "]<>[" + str(self.dxcallsign, "UTF-8") @@ -1952,7 +1982,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="close", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -1987,13 +2017,13 @@ class DATA: helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(mycallsign, "UTF-8") + "]<>[" + str(self.dxcallsign, "UTF-8") @@ -2002,7 +2032,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="close", mycallsign=str(mycallsign, 'UTF-8'), @@ -2016,7 +2046,7 @@ class DATA: def transmit_session_heartbeat(self) -> None: """Send ARQ sesion heartbeat while connected""" # ARQ.arq_session = True - # TNC.tnc_state = "BUSY" + # Modem.modem_state = "BUSY" # ARQ.arq_session_state = "connected" connection_frame = bytearray(self.length_sig0_frame) @@ -2024,7 +2054,7 @@ class DATA: connection_frame[1:2] = self.session_id self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connected", heartbeat="transmitting", @@ -2045,7 +2075,7 @@ class DATA: _valid_crc, _ = helpers.check_callsign(self.dxcallsign, bytes(data_in[4:7])) _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2])) if _valid_crc or _valid_session and ARQ.arq_session_state in ["connected", "connecting"]: - self.log.debug("[TNC] Received session heartbeat") + self.log.debug("[Modem] Received session heartbeat") Station.dxgrid = b'------' helpers.add_to_heard_stations( self.dxcallsign, @@ -2057,7 +2087,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="connected", heartbeat="received", @@ -2067,7 +2097,7 @@ class DATA: ARQ.arq_session = True ARQ.arq_session_state = "connected" - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" # Update the timeout timestamps self.arq_session_last_received = int(time.time()) @@ -2122,7 +2152,7 @@ class DATA: # override session connection attempts self.data_channel_max_retries = attempts - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" self.arq_file_transfer = True self.transmission_uuid = transmission_uuid @@ -2142,7 +2172,7 @@ class DATA: self.arq_open_data_channel(mycallsign) # wait until data channel is open - while not ARQ.arq_state and not self.datachannel_timeout and TNC.tnc_state in ["BUSY"]: + while not ARQ.arq_state and not self.datachannel_timeout and Modem.modem_state in ["BUSY"]: threading.Event().wait(0.01) if ARQ.arq_state: @@ -2173,13 +2203,13 @@ class DATA: # Update data_channel timestamp self.data_channel_last_received = int(time.time()) - if TNC.low_bandwidth_mode: + if Modem.low_bandwidth_mode: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_N.value]) - self.log.debug("[TNC] Requesting low bandwidth mode") + self.log.debug("[Modem] Requesting low bandwidth mode") else: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value]) - self.log.debug("[TNC] Requesting high bandwidth mode") + self.log.debug("[Modem] Requesting high bandwidth mode") connection_frame = bytearray(self.length_sig0_frame) connection_frame[:1] = frametype @@ -2193,7 +2223,7 @@ class DATA: for attempt in range(self.data_channel_max_retries): self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="opening", mycallsign=str(mycallsign, 'UTF-8'), @@ -2202,7 +2232,7 @@ class DATA: ) self.log.info( - "[TNC] ARQ | DATA | TX | [" + "[Modem] ARQ | DATA | TX | [" + str(mycallsign, "UTF-8") + "]>> <<[" + str(self.dxcallsign, "UTF-8") @@ -2212,9 +2242,9 @@ class DATA: # Let's check if we have a busy channel and if we are not in a running arq session. if ModemParam.channel_busy and not ARQ.arq_state: - self.log.warning("[TNC] Channel busy, waiting until free...") + self.log.warning("[Modem] Channel busy, waiting until free...") self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="waiting", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2235,17 +2265,17 @@ class DATA: # Stop waiting if data channel is opened if ARQ.arq_state: return True - if TNC.tnc_state in ["IDLE"]: + if Modem.modem_state in ["IDLE"]: return False # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up self.log.debug( - "[TNC] arq_open_data_channel:", transmission_uuid=self.transmission_uuid + "[Modem] arq_open_data_channel:", transmission_uuid=self.transmission_uuid ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", reason="unknown", @@ -2255,11 +2285,13 @@ class DATA: compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + nacks=self.frame_nack_counter, + speed_list=ARQ.speed_list ) self.log.warning( - "[TNC] ARQ | TX | DATA [" + "[Modem] ARQ | TX | DATA [" + str(mycallsign, "UTF-8") + "]>>X<<[" + str(self.dxcallsign, "UTF-8") @@ -2288,12 +2320,12 @@ class DATA: # is intended for this station. # stop processing if we don't want to respond to a call when not in a arq session - if not TNC.respond_to_call and not ARQ.arq_session: + if not Modem.respond_to_call and not ARQ.arq_session: return False - # stop processing if not in arq session, but tnc state is busy and we have a different session id + # stop processing if not in arq session, but modem state is busy and we have a different session id # use-case we get a connection request while connecting to another station - if not ARQ.arq_session and TNC.tnc_state in ["BUSY"] and data_in[13:14] != self.session_id: + if not ARQ.arq_session and Modem.modem_state in ["BUSY"] and data_in[13:14] != self.session_id: return False self.arq_file_transfer = True @@ -2316,7 +2348,7 @@ class DATA: Station.dxcallsign = self.dxcallsign self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="opening", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2335,7 +2367,7 @@ class DATA: # ISS(n) <-> IRS(w) # ISS(n) <-> IRS(n) - if frametype == FR_TYPE.ARQ_DC_OPEN_W.value and not TNC.low_bandwidth_mode: + if frametype == FR_TYPE.ARQ_DC_OPEN_W.value and not Modem.low_bandwidth_mode: # ISS(w) <-> IRS(w) constellation = "ISS(w) <-> IRS(w)" self.received_LOW_BANDWIDTH_MODE = False @@ -2349,7 +2381,7 @@ class DATA: self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw self.snr_list = self.snr_list_low_bw - elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value and not TNC.low_bandwidth_mode: + elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value and not Modem.low_bandwidth_mode: # ISS(n) <-> IRS(w) constellation = "ISS(n) <-> IRS(w)" self.received_LOW_BANDWIDTH_MODE = True @@ -2374,7 +2406,7 @@ class DATA: # initially set speed_level 0 in case of bad SNR and no matching mode self.speed_level = 0 - # TODO: MOVE THIS TO arq_calculate_speed_level() + # TODO MOVE THIS TO arq_calculate_speed_level() # calculate speed level in correlation to latest known SNR for i in range(len(self.mode_list)): if ModemParam.snr >= self.snr_list[i]: @@ -2386,13 +2418,13 @@ class DATA: if mode_slots in [ModemParam.channel_busy_slot]: self.speed_level = 0 self.log.warning( - "[TNC] busy slot detection", + "[Modem] busy slot detection", slots=ModemParam.channel_busy_slot, mode_slots=mode_slots, ) self.log.debug( - "[TNC] calculated speed level", + "[Modem] calculated speed level", speed_level=self.speed_level, given_snr=ModemParam.snr, min_snr=self.snr_list[self.speed_level], @@ -2404,7 +2436,7 @@ class DATA: helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, @@ -2416,7 +2448,7 @@ class DATA: _, self.mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) self.log.info( - "[TNC] ARQ | DATA | RX | [" + "[Modem] ARQ | DATA | RX | [" + str(self.mycallsign, "UTF-8") + "]>> <<[" + str(self.dxcallsign, "UTF-8") @@ -2431,17 +2463,17 @@ class DATA: # Set ARQ State AFTER resetting timeouts # this avoids timeouts starting too early ARQ.arq_state = True - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" self.reset_statistics() - # Select the frame type based on the current TNC mode - if TNC.low_bandwidth_mode or self.received_LOW_BANDWIDTH_MODE: + # Select the frame type based on the current Modem mode + if Modem.low_bandwidth_mode or self.received_LOW_BANDWIDTH_MODE: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_N.value]) - self.log.debug("[TNC] Responding with low bandwidth mode") + self.log.debug("[Modem] Responding with low bandwidth mode") else: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_W.value]) - self.log.debug("[TNC] Responding with high bandwidth mode") + self.log.debug("[Modem] Responding with high bandwidth mode") connection_frame = bytearray(self.length_sig0_frame) connection_frame[:1] = frametype @@ -2452,7 +2484,7 @@ class DATA: self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="opened", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2461,7 +2493,7 @@ class DATA: ) self.log.info( - "[TNC] ARQ | DATA | RX | [" + "[Modem] ARQ | DATA | RX | [" + str(self.mycallsign, "UTF-8") + "]>>|<<[" + str(self.dxcallsign, "UTF-8") @@ -2487,7 +2519,7 @@ class DATA: protocol_version = int.from_bytes(bytes(data_in[13:14]), "big") if protocol_version == ARQ.arq_protocol_version: self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="opened", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2500,29 +2532,29 @@ class DATA: self.received_LOW_BANDWIDTH_MODE = True self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw - self.log.debug("[TNC] low bandwidth mode", modes=self.mode_list) + self.log.debug("[Modem] low bandwidth mode", modes=self.mode_list) else: self.received_LOW_BANDWIDTH_MODE = False self.mode_list = self.mode_list_high_bw self.time_list = self.time_list_high_bw - self.log.debug("[TNC] high bandwidth mode", modes=self.mode_list) + self.log.debug("[Modem] high bandwidth mode", modes=self.mode_list) # set speed level from session opener frame which is selected by SNR measurement self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") - self.log.debug("[TNC] speed level selected for given SNR", speed_level=self.speed_level) + self.log.debug("[Modem] speed level selected for given SNR", speed_level=self.speed_level) # self.speed_level = len(self.mode_list) - 1 Station.dxgrid = b'------' helpers.add_to_heard_stations( Station.dxcallsign, Station.dxgrid, - "DATA-CHANNEL", + "DATA", ModemParam.snr, ModemParam.frequency_offset, HamlibParam.hamlib_frequency, ) self.log.info( - "[TNC] ARQ | DATA | TX | [" + "[Modem] ARQ | DATA | TX | [" + str(self.mycallsign, "UTF-8") + "]>>|<<[" + str(self.dxcallsign, "UTF-8") @@ -2535,10 +2567,10 @@ class DATA: # Update data_channel timestamp self.data_channel_last_received = int(time.time()) else: - TNC.tnc_state = "IDLE" + Modem.modem_state = "IDLE" ARQ.arq_state = False self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", reason="protocol version missmatch", @@ -2546,9 +2578,9 @@ class DATA: dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) ) - # TODO: We should display a message to this effect on the UI. + # TODO We should display a message to this effect on the UI. self.log.warning( - "[TNC] protocol version mismatch:", + "[Modem] protocol version mismatch:", received=protocol_version, own=ARQ.arq_protocol_version, ) @@ -2564,20 +2596,20 @@ class DATA: """ if not str(dxcallsign).strip(): - # TODO: We should display a message to this effect on the UI. - self.log.warning("[TNC] Missing required callsign", dxcallsign=dxcallsign) + # TODO We should display a message to this effect on the UI. + self.log.warning("[Modem] Missing required callsign", dxcallsign=dxcallsign) return Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", ping="transmitting", dxcallsign=str(dxcallsign, "UTF-8"), mycallsign=str(mycallsign, "UTF-8"), snr=str(ModemParam.snr), ) self.log.info( - "[TNC] PING REQ [" + "[Modem] PING REQ [" + str(mycallsign, "UTF-8") + "] >>> [" + str(dxcallsign, "UTF-8") @@ -2590,8 +2622,8 @@ class DATA: ping_frame[4:7] = helpers.get_crc_24(mycallsign) ping_frame[7:13] = helpers.callsign_to_bytes(mycallsign) - if TNC.enable_fsk: - self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) + if Modem.enable_fsk: + self.log.info("[Modem] ENABLE FSK", state=Modem.enable_fsk) self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) @@ -2611,13 +2643,13 @@ class DATA: valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) if not valid: # PING packet not for me. - self.log.debug("[TNC] received_ping: ping not for this station.") + self.log.debug("[Modem] received_ping: ping not for this station.") return Station.dxcallsign_crc = dxcallsign_crc Station.dxcallsign = dxcallsign self.log.info( - "[TNC] PING REQ [" + "[Modem] PING REQ [" + str(mycallsign, "UTF-8") + "] <<< [" + str(dxcallsign, "UTF-8") @@ -2636,7 +2668,7 @@ class DATA: ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", ping="received", uuid=str(uuid.uuid4()), timestamp=int(time.time()), @@ -2645,7 +2677,7 @@ class DATA: mycallsign=str(mycallsign, "UTF-8"), snr=str(ModemParam.snr), ) - if TNC.respond_to_call: + if Modem.respond_to_call: self.transmit_ping_ack() def transmit_ping_ack(self): @@ -2661,7 +2693,7 @@ class DATA: ping_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) ping_frame[13:14] = helpers.snr_to_bytes(ModemParam.snr) - if TNC.enable_fsk: + if Modem.enable_fsk: self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) @@ -2682,7 +2714,7 @@ class DATA: Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") dxsnr = helpers.snr_from_bytes(data_in[13:14]) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", ping="acknowledge", uuid=str(uuid.uuid4()), timestamp=int(time.time()), @@ -2704,7 +2736,7 @@ class DATA: ) self.log.info( - "[TNC] PING ACK [" + "[Modem] PING ACK [" + str(mycallsign, "UTF-8") + "] >|< [" + str(Station.dxcallsign, "UTF-8") @@ -2712,10 +2744,10 @@ class DATA: snr=ModemParam.snr, dxsnr=dxsnr, ) - TNC.tnc_state = "IDLE" + Modem.modem_state = "IDLE" else: self.log.info( - "[TNC] FOREIGN PING ACK [" + "[Modem] FOREIGN PING ACK [" + str(self.mycallsign, "UTF-8") + "] ??? [" + str(bytes(data_in[4:7]), "UTF-8") @@ -2727,12 +2759,12 @@ class DATA: """ Force a stop of the running transmission """ - self.log.warning("[TNC] Stopping transmission!") + self.log.warning("[Modem] Stopping transmission!") - TNC.tnc_state = "IDLE" + Modem.modem_state = "IDLE" ARQ.arq_state = False self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="stopped", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2743,7 +2775,7 @@ class DATA: stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value]) stop_frame[1:4] = Station.dxcallsign_crc stop_frame[4:7] = Station.mycallsign_crc - # TODO: Not sure if we really need the session id when disconnecting + # TODO Not sure if we really need the session id when disconnecting # stop_frame[1:2] = self.session_id stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) @@ -2757,11 +2789,11 @@ class DATA: """ Received a transmission stop """ - self.log.warning("[TNC] Stopping transmission!") - TNC.tnc_state = "IDLE" + self.log.warning("[Modem] Stopping transmission!") + Modem.modem_state = "IDLE" ARQ.arq_state = False self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="stopped", mycallsign=str(self.mycallsign, 'UTF-8'), @@ -2789,18 +2821,18 @@ class DATA: not ARQ.arq_session and not self.arq_file_transfer and not Beacon.beacon_pause - and not ModemParam.channel_busy - and TNC.tnc_state not in ["BUSY"] + #and not ModemParam.channel_busy + and Modem.modem_state not in ["BUSY"] and not ARQ.arq_state ): self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", beacon="transmitting", dxcallsign="None", interval=self.beacon_interval, ) self.log.info( - "[TNC] Sending beacon!", interval=self.beacon_interval + "[Modem] Sending beacon!", interval=self.beacon_interval ) beacon_frame = bytearray(self.length_sig0_frame) @@ -2808,8 +2840,8 @@ class DATA: beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) beacon_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) - if TNC.enable_fsk: - self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) + if Modem.enable_fsk: + self.log.info("[Modem] ENABLE FSK", state=Modem.enable_fsk) self.enqueue_frame_for_tx( [beacon_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value, @@ -2827,7 +2859,7 @@ class DATA: threading.Event().wait(0.01) except Exception as err: - self.log.debug("[TNC] run_beacon: ", exception=err) + self.log.debug("[Modem] run_beacon: ", exception=err) def received_beacon(self, data_in: bytes) -> None: """ @@ -2840,7 +2872,7 @@ class DATA: beacon_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", beacon="received", uuid=str(uuid.uuid4()), timestamp=int(time.time()), @@ -2850,7 +2882,7 @@ class DATA: ) self.log.info( - "[TNC] BEACON RCVD [" + "[Modem] BEACON RCVD [" + str(beacon_callsign, "UTF-8") + "][" + str(Station.dxgrid, "UTF-8") @@ -2875,9 +2907,9 @@ class DATA: Returns: Nothing """ - self.log.info("[TNC] CQ CQ CQ") + self.log.info("[Modem] CQ CQ CQ") self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", cq="transmitting", mycallsign=str(self.mycallsign, "UTF-8"), dxcallsign="None", @@ -2887,15 +2919,15 @@ class DATA: cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) cq_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) - self.log.debug("[TNC] CQ Frame:", data=[cq_frame]) + self.log.debug("[Modem] CQ Frame:", data=[cq_frame]) - if TNC.enable_fsk: - self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) + if Modem.enable_fsk: + self.log.info("[Modem] ENABLE FSK", state=Modem.enable_fsk) self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - # FIXME: Remove or change this in later versions for full CW support - # TNC.transmitting = True + # FIXME Remove or change this in later versions for full CW support + # Modem.transmitting = True # modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, "123"]) def received_cq(self, data_in: bytes) -> None: @@ -2909,18 +2941,18 @@ class DATA: """ # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - self.log.debug("[TNC] received_cq:", dxcallsign=dxcallsign) + self.log.debug("[Modem] received_cq:", dxcallsign=dxcallsign) Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", cq="received", mycallsign=str(self.mycallsign, "UTF-8"), dxcallsign=str(dxcallsign, "UTF-8"), dxgrid=str(Station.dxgrid, "UTF-8"), ) self.log.info( - "[TNC] CQ RCVD [" + "[Modem] CQ RCVD [" + str(dxcallsign, "UTF-8") + "][" + str(Station.dxgrid, "UTF-8") @@ -2936,7 +2968,7 @@ class DATA: HamlibParam.hamlib_frequency, ) - if TNC.respond_to_cq and TNC.respond_to_call: + if Modem.respond_to_cq and Modem.respond_to_call: self.transmit_qrv(dxcallsign) def transmit_qrv(self, dxcallsign: bytes) -> None: @@ -2952,17 +2984,17 @@ class DATA: # duration, plus overhead. Set the wait interval to be random between 0 and # self.duration_sig1_frame * 4 == 4 slots # in self.duration_sig1_frame increments. - # FIXME: This causes problems when running ctests - we need to figure out why + # FIXME This causes problems when running ctests - we need to figure out why if not TESTMODE: - self.log.info("[TNC] Waiting for QRV slot...") + self.log.info("[Modem] Waiting for QRV slot...") helpers.wait(randrange(0, int(self.duration_sig1_frame * 4), self.duration_sig1_frame * 10 // 10.0)) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", qrv="transmitting", dxcallsign=str(dxcallsign, "UTF-8"), ) - self.log.info("[TNC] Sending QRV!") + self.log.info("[Modem] Sending QRV!") qrv_frame = bytearray(self.length_sig0_frame) qrv_frame[:1] = bytes([FR_TYPE.QRV.value]) @@ -2970,8 +3002,8 @@ class DATA: qrv_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) qrv_frame[11:12] = helpers.snr_to_bytes(ModemParam.snr) - if TNC.enable_fsk: - self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) + if Modem.enable_fsk: + self.log.info("[Modem] ENABLE FSK", state=Modem.enable_fsk) self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) @@ -2991,7 +3023,7 @@ class DATA: combined_snr = f"{ModemParam.snr}/{dxsnr}" self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", qrv="received", dxcallsign=str(dxcallsign, "UTF-8"), dxgrid=str(Station.dxgrid, "UTF-8"), @@ -3000,7 +3032,7 @@ class DATA: ) self.log.info( - "[TNC] QRV RCVD [" + "[Modem] QRV RCVD [" + str(dxcallsign, "UTF-8") + "][" + str(Station.dxgrid, "UTF-8") @@ -3030,13 +3062,13 @@ class DATA: dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", fec="is_writing", dxcallsign=str(dxcallsign, "UTF-8") ) self.log.info( - "[TNC] IS_WRITING RCVD [" + "[Modem] IS_WRITING RCVD [" + str(dxcallsign, "UTF-8") + "] ", ) @@ -3090,7 +3122,7 @@ class DATA: ARQ.bytes_per_minute = 0 ARQ.arq_seconds_until_finish = 0 except Exception as err: - self.log.error(f"[TNC] calculate_transfer_rate_rx: Exception: {err}") + self.log.error(f"[Modem] calculate_transfer_rate_rx: Exception: {err}") ARQ.arq_transmission_percent = 0.0 ARQ.arq_bits_per_second = 0 ARQ.bytes_per_minute = 0 @@ -3154,7 +3186,7 @@ class DATA: ARQ.arq_seconds_until_finish = 0 except Exception as err: - self.log.error(f"[TNC] calculate_transfer_rate_tx: Exception: {err}") + self.log.error(f"[Modem] calculate_transfer_rate_tx: Exception: {err}") ARQ.arq_transmission_percent = 0.0 ARQ.arq_bits_per_second = 0 ARQ.bytes_per_minute = 0 @@ -3171,10 +3203,10 @@ class DATA: Cleanup function which clears all ARQ states """ if TESTMODE: - self.log.debug("[TNC] TESTMODE: arq_cleanup: Not performing cleanup.") + self.log.debug("[Modem] TESTMODE: arq_cleanup: Not performing cleanup.") return - self.log.debug("[TNC] arq_cleanup") + self.log.debug("[Modem] arq_cleanup") # wait a second for smoother arq behaviour helpers.wait(1.0) @@ -3223,7 +3255,7 @@ class DATA: # we need to keep these values if in ARQ_SESSION if not ARQ.arq_session: - TNC.tnc_state = "IDLE" + Modem.modem_state = "IDLE" self.dxcallsign = b"AA0AA-0" self.mycallsign = Station.mycallsign self.session_id = bytes(1) @@ -3268,32 +3300,32 @@ class DATA: modem.RECEIVE_DATAC3 = False modem.RECEIVE_DATAC4 = False modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[TNC] Changing listening data mode", mode="datac1") + self.log.debug("[Modem] Changing listening data mode", mode="datac1") elif mode == codec2.FREEDV_MODE.datac3.value: modem.RECEIVE_DATAC1 = False modem.RECEIVE_DATAC3 = True modem.RECEIVE_DATAC4 = False modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[TNC] Changing listening data mode", mode="datac3") + self.log.debug("[Modem] Changing listening data mode", mode="datac3") elif mode == codec2.FREEDV_MODE.datac4.value: modem.RECEIVE_DATAC1 = False modem.RECEIVE_DATAC3 = False modem.RECEIVE_DATAC4 = True modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[TNC] Changing listening data mode", mode="datac4") + self.log.debug("[Modem] Changing listening data mode", mode="datac4") elif mode == codec2.FREEDV_MODE.fsk_ldpc_1.value: modem.RECEIVE_DATAC1 = False modem.RECEIVE_DATAC3 = False modem.RECEIVE_DATAC4 = False modem.RECEIVE_FSK_LDPC_1 = True - self.log.debug("[TNC] Changing listening data mode", mode="fsk_ldpc_1") + self.log.debug("[Modem] Changing listening data mode", mode="fsk_ldpc_1") else: modem.RECEIVE_DATAC1 = True modem.RECEIVE_DATAC3 = True modem.RECEIVE_DATAC4 = True modem.RECEIVE_FSK_LDPC_1 = True self.log.debug( - "[TNC] Changing listening data mode", mode="datac1/datac3/fsk_ldpc" + "[Modem] Changing listening data mode", mode="datac1/datac3/fsk_ldpc" ) # ------------------------- WATCHDOG FUNCTIONS FOR TIMER @@ -3315,7 +3347,7 @@ class DATA: DATA BURST """ # IRS SIDE - # TODO: We need to redesign this part for cleaner state handling + # TODO We need to redesign this part for cleaner state handling # Return if not ARQ STATE and not ARQ SESSION STATE as they are different use cases if ( not ARQ.arq_state @@ -3344,11 +3376,11 @@ class DATA: frames_left = 1 timeout = self.burst_last_received + (self.time_list[self.speed_level] * frames_left) - # TODO: Enable this for development + # TODO Enable this for development # print(f"timeout expected in:{round(timeout - time.time())} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") if timeout <= time.time() or modem_error_state: self.log.warning( - "[TNC] Burst decoding error or timeout", + "[Modem] Burst decoding error or timeout", attempt=self.n_retries_per_burst, max_attempts=self.rx_n_max_retries_per_burst, speed_level=self.speed_level, @@ -3377,7 +3409,7 @@ class DATA: self.speed_level = max(self.speed_level - 1, 0) ARQ.arq_speed_level = self.speed_level - # TODO: Create better mechanisms for handling n frames per burst for bad channels + # TODO Create better mechanisms for handling n frames per burst for bad channels # reduce frames per burst if self.burst_rpt_counter >= 2: tx_n_frames_per_burst = max(self.rx_n_frames_per_burst - 1, 1) @@ -3387,11 +3419,11 @@ class DATA: # Update modes we are listening to self.set_listening_modes(True, True, self.mode_list[self.speed_level]) - # TODO: Does SNR make sense for NACK if we dont have an actual SNR information? + # TODO Does SNR make sense for NACK if we dont have an actual SNR information? self.send_burst_nack_frame_watchdog(0, tx_n_frames_per_burst) # Update data_channel timestamp - # TODO: Disabled this one for testing. + # TODO Disabled this one for testing. # self.data_channel_last_received = time.time() self.n_retries_per_burst += 1 else: @@ -3407,7 +3439,7 @@ class DATA: DATA CHANNEL """ # and not static.ARQ_SEND_KEEP_ALIVE: - if ARQ.arq_state and TNC.tnc_state == "BUSY": + if ARQ.arq_state and Modem.modem_state == "BUSY": threading.Event().wait(0.01) if ( self.data_channel_last_received + self.transmission_timeout @@ -3415,6 +3447,7 @@ class DATA: ): timeleft = int((self.data_channel_last_received + self.transmission_timeout) - time.time()) + ARQ.arq_seconds_until_timeout = timeleft if timeleft % 10 == 0: self.log.debug("Time left until channel timeout", seconds=timeleft) @@ -3425,14 +3458,14 @@ class DATA: # Clear the timeout timestamp self.data_channel_last_received = 0 self.log.info( - "[TNC] DATA [" + "[Modem] DATA [" + str(self.mycallsign, "UTF-8") + "]<>[" + str(Station.dxcallsign, "UTF-8") + "]" ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="transmission", status="failed", uuid=self.transmission_uuid, @@ -3449,21 +3482,21 @@ class DATA: """ if ( ARQ.arq_session - and TNC.tnc_state == "BUSY" + and Modem.modem_state == "BUSY" and not self.arq_file_transfer ): if self.arq_session_last_received + self.arq_session_timeout > time.time(): threading.Event().wait(0.01) else: self.log.info( - "[TNC] SESSION [" + "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") + "]<>[" + str(self.dxcallsign, "UTF-8") + "]" ) self.send_data_to_socket_queue( - freedata="tnc-message", + freedata="modem-message", arq="session", status="failed", reason="timeout", @@ -3539,7 +3572,7 @@ class DATA: # send burst only if channel not busy - but without waiting # otherwise burst will be dropped - if not ModemParam.channel_busy and not TNC.transmitting: + if not ModemParam.channel_busy and not Modem.transmitting: self.enqueue_frame_for_tx( frame_to_tx=[fec_frame], c2_mode=codec2.FREEDV_MODE["sig0"].value ) @@ -3563,7 +3596,7 @@ class DATA: try: - self.log.info("[TNC] ARQ | RX | saving data to folder") + self.log.info("[Modem] ARQ | RX | saving data to folder") mycallsign = str(mycallsign, "UTF-8") dxcallsign = str(dxcallsign, "UTF-8") @@ -3606,7 +3639,7 @@ class DATA: crc = helpers.get_crc_32(data).hex().lower() validity = checksum_delivered == crc self.log.info( - "[TNC] ARQ | RX | checking data crc", + "[Modem] ARQ | RX | checking data crc", crc_delivered=checksum_delivered, crc_calculated=crc, valid=validity, @@ -3623,4 +3656,4 @@ class DATA: file.write(message) except Exception as e: - self.log.error("[TNC] error saving data to folder", e=e) \ No newline at end of file + self.log.error("[Modem] error saving data to folder", e=e) \ No newline at end of file diff --git a/tnc/exceptions.py b/modem/exceptions.py similarity index 100% rename from tnc/exceptions.py rename to modem/exceptions.py diff --git a/tnc/explorer.py b/modem/explorer.py similarity index 90% rename from tnc/explorer.py rename to modem/explorer.py index 9b5c5444..e6a65bcc 100644 --- a/tnc/explorer.py +++ b/modem/explorer.py @@ -13,7 +13,7 @@ import time import ujson as json import structlog import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem log = structlog.get_logger("explorer") @@ -38,8 +38,8 @@ class explorer(): band = "USB" callsign = str(Station.mycallsign, "utf-8") gridsquare = str(Station.mygrid, "utf-8") - version = str(TNC.version) - bandwidth = str(TNC.low_bandwidth_mode) + version = str(Modem.version) + bandwidth = str(Modem.low_bandwidth_mode) beacon = str(Beacon.beacon_state) strength = str(HamlibParam.hamlib_strength) @@ -48,7 +48,7 @@ class explorer(): headers = {"Content-Type": "application/json"} station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'strength': strength, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []} - for i in TNC.heard_stations: + for i in Modem.heard_stations: try: callsign = str(i[0], "UTF-8") grid = str(i[1], "UTF-8") diff --git a/tnc/freedata.spec b/modem/freedata.spec similarity index 83% rename from tnc/freedata.spec rename to modem/freedata.spec index feb115de..6ae5fdc1 100644 --- a/tnc/freedata.spec +++ b/modem/freedata.spec @@ -41,8 +41,8 @@ daemon_a.datas += Tree('lib', prefix='lib') # daemon_a.datas += Tree('./codec2', prefix='codec2') -# TNC -------------------------------------------------- -tnc_a = Analysis(['main.py'], +# Modem -------------------------------------------------- +modem_a = Analysis(['main.py'], pathex=[], binaries=[], datas=[], @@ -55,15 +55,15 @@ tnc_a = Analysis(['main.py'], win_private_assemblies=False, cipher=block_cipher, noarchive=False) -tnc_pyz = PYZ(tnc_a.pure, tnc_a.zipped_data, +modem_pyz = PYZ(modem_a.pure, modem_a.zipped_data, cipher=block_cipher) -tnc_exe = EXE(tnc_pyz, - tnc_a.scripts, +modem_exe = EXE(modem_pyz, + modem_a.scripts, [], exclude_binaries=True, - name='freedata-tnc', - bundle_identifier='com.dj2ls.freedata-tnc', + name='freedata-modem', + bundle_identifier='com.dj2ls.freedata-modem', debug=False, bootloader_ignore_signals=False, strip=False, @@ -79,11 +79,11 @@ coll = COLLECT(daemon_exe, daemon_a.binaries, daemon_a.zipfiles, daemon_a.datas, - tnc_exe, - tnc_a.binaries, - tnc_a.zipfiles, - tnc_a.datas, + modem_exe, + modem_a.binaries, + modem_a.zipfiles, + modem_a.datas, strip=False, upx=True, upx_exclude=[], - name='tnc') + name='modem') diff --git a/modem/global_instances.py b/modem/global_instances.py new file mode 100644 index 00000000..15a1473f --- /dev/null +++ b/modem/global_instances.py @@ -0,0 +1,19 @@ +# global_instances.py + +from static import Daemon, ARQ, AudioParam, Beacon, Channel, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam + +# Initialize instances with appropriate default values + +# Create single instances of each dataclass +Daemon = Daemon(modemprocess=None, rigctldprocess=None) +ARQ = ARQ() +AudioParam = AudioParam() +Beacon = Beacon() +Channel = Channel() +HamlibParam = HamlibParam() +ModemParam = ModemParam() +Station = Station() +Statistics = Statistics() +TCIParam = TCIParam() +Modem = Modem() +MeshParam = MeshParam() diff --git a/tnc/helpers.py b/modem/helpers.py similarity index 94% rename from tnc/helpers.py rename to modem/helpers.py index 844bf2f3..a2ca5026 100644 --- a/tnc/helpers.py +++ b/modem/helpers.py @@ -8,7 +8,7 @@ import time from datetime import datetime,timezone import crcengine import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam import structlog import numpy as np import threading @@ -139,16 +139,16 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): Nothing """ # check if buffer empty - if len(TNC.heard_stations) == 0: - TNC.heard_stations.append( + if len(Modem.heard_stations) == 0: + Modem.heard_stations.append( [dxcallsign, dxgrid, int(datetime.now(timezone.utc).timestamp()), datatype, snr, offset, frequency] ) # if not, we search and update else: - for i in range(len(TNC.heard_stations)): + for i in range(len(Modem.heard_stations)): # Update callsign with new timestamp - if TNC.heard_stations[i].count(dxcallsign) > 0: - TNC.heard_stations[i] = [ + if Modem.heard_stations[i].count(dxcallsign) > 0: + Modem.heard_stations[i] = [ dxcallsign, dxgrid, int(time.time()), @@ -159,8 +159,8 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): ] break # Insert if nothing found - if i == len(TNC.heard_stations) - 1: - TNC.heard_stations.append( + if i == len(Modem.heard_stations) - 1: + Modem.heard_stations.append( [ dxcallsign, dxgrid, @@ -174,10 +174,10 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): break -# for idx, item in enumerate(TNC.heard_stations): +# for idx, item in enumerate(Modem.heard_stations): # if dxcallsign in item: # item = [dxcallsign, int(time.time())] -# TNC.heard_stations[idx] = item +# Modem.heard_stations[idx] = item def callsign_to_bytes(callsign) -> bytes: @@ -530,7 +530,7 @@ def get_hmac_salt(dxcallsign: bytes, mycallsign: bytes): filepath = subfolder / filename except Exception as e: log.error( - "[TNC] [HMAC] File lookup error", file=filepath, + "[Modem] [HMAC] File lookup error", file=filepath, ) # check if file exists else return false @@ -580,13 +580,13 @@ def search_hmac_salt(dxcallsign: bytes, mycallsign: bytes, search_token, data_fr filepath = subfolder / filename except Exception as e: log.error( - "[TNC] [HMAC] File lookup error", file=filepath, + "[Modem] [HMAC] File lookup error", file=filepath, ) # check if file exists else return false if not check_if_file_exists(filepath): log.warning( - "[TNC] [HMAC] Token file not found", file=filepath, + "[Modem] [HMAC] Token file not found", file=filepath, ) return False @@ -599,7 +599,7 @@ def search_hmac_salt(dxcallsign: bytes, mycallsign: bytes, search_token, data_fr key = token_list[len(token_list) - _][:-1] key = bytes(key, "utf-8") search_digest = hmac.new(key, data_frame, hashlib.sha256).digest()[:4] - # TODO: Remove this debugging information if not needed anymore + # TODO Remove this debugging information if not needed anymore # print("-----------------------------------------") # print(_) # print(f" key-------------{key}") @@ -611,19 +611,19 @@ def search_hmac_salt(dxcallsign: bytes, mycallsign: bytes, search_token, data_fr token_position = len(token_list) - _ delete_last_line_from_hmac_list(filepath, token_position) log.info( - "[TNC] [HMAC] Signature found", expected=search_token.hex(), + "[Modem] [HMAC] Signature found", expected=search_token.hex(), ) return True log.warning( - "[TNC] [HMAC] Signature not found", expected=search_token.hex(), filepath=filepath, + "[Modem] [HMAC] Signature not found", expected=search_token.hex(), filepath=filepath, ) return False except Exception as e: log.warning( - "[TNC] [HMAC] Lookup failed", e=e, expected=search_token, + "[Modem] [HMAC] Lookup failed", e=e, expected=search_token, ) return False @@ -662,6 +662,6 @@ def check_if_file_exists(path): return False except Exception as e: log.warning( - "[TNC] [FILE] Lookup failed", e=e, path=path, + "[Modem] [FILE] Lookup failed", e=e, path=path, ) return False \ No newline at end of file diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win32.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win32.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win32.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win32.whl diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win_amd64.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win_amd64.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win_amd64.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp310-cp310-win_amd64.whl diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win32.whl diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp311-cp311-win_amd64.whl diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win32.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win32.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win32.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win32.whl diff --git a/tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win_amd64.whl b/modem/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win_amd64.whl similarity index 100% rename from tnc/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win_amd64.whl rename to modem/lib/pyaudio/windows/PyAudio-0.2.11-cp39-cp39-win_amd64.whl diff --git a/tnc/lib/pyaudio/windows/README.md b/modem/lib/pyaudio/windows/README.md similarity index 100% rename from tnc/lib/pyaudio/windows/README.md rename to modem/lib/pyaudio/windows/README.md diff --git a/tnc/log_handler.py b/modem/log_handler.py similarity index 100% rename from tnc/log_handler.py rename to modem/log_handler.py diff --git a/tnc/main.py b/modem/main.py similarity index 85% rename from tnc/main.py rename to modem/main.py index fa7ac8bb..b96be8e1 100755 --- a/tnc/main.py +++ b/modem/main.py @@ -5,11 +5,11 @@ Created on Tue Dec 22 16:58:45 2020 @author: DJ2LS -main module for running the tnc +main module for running the modem """ -# run tnc self test on startup before we are doing other things +# run modem self test on startup before we are doing other things # import selftest # selftest.TEST() @@ -29,7 +29,7 @@ import helpers import log_handler import modem import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam import structlog import explorer import json @@ -46,7 +46,7 @@ def signal_handler(sig, frame): Returns: system exit """ - print("Closing TNC...") + print("Closing Modem...") sock.CLOSE_SIGNAL = True sys.exit(0) @@ -57,7 +57,7 @@ if __name__ == "__main__": # This is for Windows multiprocessing support multiprocessing.freeze_support() # --------------------------------------------GET PARAMETER INPUTS - PARSER = argparse.ArgumentParser(description="FreeDATA TNC") + PARSER = argparse.ArgumentParser(description="FreeDATA Modem") #PARSER.add_argument( # "--use-config", @@ -205,7 +205,7 @@ if __name__ == "__main__": "--explorer", dest="enable_explorer", action="store_true", - help="Enable sending tnc data to https://explorer.freedata.app", + help="Enable sending modem data to https://explorer.freedata.app", ) PARSER.add_argument( @@ -295,27 +295,27 @@ if __name__ == "__main__": except ValueError: AudioParam.audio_output_device = ARGS.audio_output_device - TNC.port = ARGS.socket_port + Modem.port = ARGS.socket_port HamlibParam.hamlib_radiocontrol = ARGS.hamlib_radiocontrol HamlibParam.hamlib_rigctld_ip = ARGS.rigctld_ip HamlibParam.hamlib_rigctld_port = str(ARGS.rigctld_port) ModemParam.enable_scatter = ARGS.send_scatter AudioParam.enable_fft = ARGS.send_fft - TNC.enable_fsk = ARGS.enable_fsk - TNC.low_bandwidth_mode = ARGS.low_bandwidth_mode + Modem.enable_fsk = ARGS.enable_fsk + Modem.low_bandwidth_mode = ARGS.low_bandwidth_mode ModemParam.tuning_range_fmin = ARGS.tuning_range_fmin ModemParam.tuning_range_fmax = ARGS.tuning_range_fmax AudioParam.tx_audio_level = ARGS.tx_audio_level - TNC.respond_to_cq = ARGS.enable_respond_to_cq + Modem.respond_to_cq = ARGS.enable_respond_to_cq ARQ.rx_buffer_size = ARGS.rx_buffer_size - TNC.enable_explorer = ARGS.enable_explorer + Modem.enable_explorer = ARGS.enable_explorer AudioParam.audio_auto_tune = ARGS.enable_audio_auto_tune - TNC.enable_stats = ARGS.enable_stats + Modem.enable_stats = ARGS.enable_stats TCIParam.ip = ARGS.tci_ip TCIParam.port = ARGS.tci_port ModemParam.tx_delay = ARGS.tx_delay MeshParam.enable_protocol = ARGS.enable_mesh - TNC.enable_hmac = ARGS.enable_hmac + Modem.enable_hmac = ARGS.enable_hmac except Exception as e: @@ -348,25 +348,25 @@ if __name__ == "__main__": except ValueError: AudioParam.audio_output_device = conf.get('AUDIO', 'tx', '0') - TNC.port = int(conf.get('NETWORK', 'tncport', '3000')) + Modem.port = int(conf.get('NETWORK', 'modemport', '3000')) HamlibParam.hamlib_radiocontrol = conf.get('RADIO', 'radiocontrol', 'disabled') HamlibParam.hamlib_rigctld_ip = conf.get('RADIO', 'rigctld_ip', '127.0.0.1') HamlibParam.hamlib_rigctld_port = str(conf.get('RADIO', 'rigctld_port', '4532')) - ModemParam.enable_scatter = conf.get('TNC', 'scatter', 'True') - AudioParam.enable_fft = conf.get('TNC', 'fft', 'True') - TNC.enable_fsk = conf.get('TNC', 'fsk', 'False') - TNC.low_bandwidth_mode = conf.get('TNC', 'narrowband', 'False') - ModemParam.tuning_range_fmin = float(conf.get('TNC', 'fmin', '-50.0')) - ModemParam.tuning_range_fmax = float(conf.get('TNC', 'fmax', '50.0')) + ModemParam.enable_scatter = conf.get('Modem', 'scatter', 'True') + AudioParam.enable_fft = conf.get('Modem', 'fft', 'True') + Modem.enable_fsk = conf.get('Modem', 'fsk', 'False') + Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False') + ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0')) + ModemParam.tuning_range_fmax = float(conf.get('Modem', 'fmax', '50.0')) AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '100')) - TNC.respond_to_cq = conf.get('TNC', 'qrv', 'True') - ARQ.rx_buffer_size = int(conf.get('TNC', 'rx_buffer_size', '16')) - TNC.enable_explorer = conf.get('TNC', 'explorer', 'False') + Modem.respond_to_cq = conf.get('Modem', 'qrv', 'True') + ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16')) + Modem.enable_explorer = conf.get('Modem', 'explorer', 'False') AudioParam.audio_auto_tune = conf.get('AUDIO', 'auto_tune', 'False') - TNC.enable_stats = conf.get('TNC', 'stats', 'False') + Modem.enable_stats = conf.get('Modem', 'stats', 'False') TCIParam.ip = str(conf.get('TCI', 'tci_ip', 'localhost')) TCIParam.port = int(conf.get('TCI', 'tci_port', '50001')) - ModemParam.tx_delay = int(conf.get('TNC', 'tx_delay', '0')) + ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0')) MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False') except KeyError as e: log.warning("[CFG] Error reading config file near", key=str(e)) @@ -384,7 +384,7 @@ if __name__ == "__main__": # config logging try: if sys.platform == "linux": - logging_path = os.getenv("HOME") + "/.config/" + "FreeDATA/" + "tnc" + logging_path = os.getenv("HOME") + "/.config/" + "FreeDATA/" + "modem" if sys.platform == "darwin": logging_path = ( @@ -392,11 +392,11 @@ if __name__ == "__main__": + "/Library/" + "Application Support/" + "FreeDATA/" - + "tnc" + + "modem" ) if sys.platform in ["win32", "win64"]: - logging_path = os.getenv("APPDATA") + "/" + "FreeDATA/" + "tnc" + logging_path = os.getenv("APPDATA") + "/" + "FreeDATA/" + "modem" if not os.path.exists(logging_path): os.makedirs(logging_path) @@ -405,7 +405,7 @@ if __name__ == "__main__": log.error("[DMN] logger init error", exception=err) log.info( - "[TNC] Starting FreeDATA", author="DJ2LS", version=TNC.version + "[Modem] Starting FreeDATA", author="DJ2LS", version=Modem.version ) # start data handler @@ -421,17 +421,17 @@ if __name__ == "__main__": mesh = mesh.MeshRouter() # optionally start explorer module - if TNC.enable_explorer: - log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=TNC.enable_explorer) + if Modem.enable_explorer: + log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=Modem.enable_explorer) explorer = explorer.explorer() # --------------------------------------------START CMD SERVER try: - log.info("[TNC] Starting TCP/IP socket", port=TNC.port) + log.info("[Modem] Starting TCP/IP socket", port=Modem.port) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True cmdserver = sock.ThreadedTCPServer( - (TNC.host, TNC.port), sock.ThreadedTCPRequestHandler + (Modem.host, Modem.port), sock.ThreadedTCPRequestHandler ) server_thread = threading.Thread(target=cmdserver.serve_forever) @@ -439,7 +439,7 @@ if __name__ == "__main__": server_thread.start() except Exception as err: - log.error("[TNC] Starting TCP/IP socket failed", port=TNC.port, e=err) + log.error("[Modem] Starting TCP/IP socket failed", port=Modem.port, e=err) sys.exit(1) while True: threading.Event().wait(1) \ No newline at end of file diff --git a/tnc/mesh.py b/modem/mesh.py similarity index 97% rename from tnc/mesh.py rename to modem/mesh.py index 3fb331eb..a08303ef 100644 --- a/tnc/mesh.py +++ b/modem/mesh.py @@ -15,7 +15,7 @@ HF mesh networking prototype and testing module -TODO: SIGNALLING FOR ACK/NACK: +TODO SIGNALLING FOR ACK/NACK: - mesh-signalling burst is datac13 - mesh-signalling frame contains [message id, status, hops, score, payload] - frame type is 1 byte @@ -29,14 +29,16 @@ TODO: SIGNALLING FOR ACK/NACK: - if done already in list, don't reset retry counter - delete ACK/NACK if "done" and timestamp older than 1day -TODO: SCORE CALCULATION: +TODO SCORE CALCULATION: SNR: negative --> * 2 """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member # pylint: disable=import-outside-toplevel, attribute-defined-outside-init -from static import TNC, MeshParam, FRAME_TYPE, Station, ModemParam, ARQ +from static import FRAME_TYPE +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem + from codec2 import FREEDV_MODE import numpy as np import time @@ -111,7 +113,7 @@ class MeshRouter(): heard stations format: [dxcallsign,dxgrid,int(time.time()),datatype,snr,offset,frequency] - TNC.heard_stations.append( + Modem.heard_stations.append( [ dxcallsign, dxgrid, @@ -132,7 +134,7 @@ class MeshRouter(): frequency_position = 6 try: - for item in TNC.heard_stations: + for item in Modem.heard_stations: #print("-----------") #print(item) #print(item[snr_position]) @@ -220,13 +222,13 @@ class MeshRouter(): #print(len(_)) frame_list.append(mesh_broadcast_frame_header + _) - TNC.transmitting = True + Modem.transmitting = True c2_mode = FREEDV_MODE.datac4.value self.log.info("[MESH] broadcasting routing table", frame_list=frame_list, frames=len(split_result)) modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, 1, 0, frame_list]) # Wait while transmitting - while TNC.transmitting: + while Modem.transmitting: threading.Event().wait(0.01) except Exception as e: self.log.warning("[MESH] broadcasting routing table", e=e) @@ -410,7 +412,7 @@ class MeshRouter(): self.add_mesh_ping_to_signalling_table(destination, origin, frametype="PING", status="forwarding") def received_mesh_ping_ack(self, data_in): - # TODO: + # TODO # Check if we have a ping callsign already in signalling table # if PING, then override and make it a PING-ACK # if not, then add to table @@ -520,7 +522,7 @@ class MeshRouter(): repeat_delay=0, ) -> None: """ - Send (transmit) supplied frame to TNC + Send (transmit) supplied frame to Modem :param frame_to_tx: Frame data to send :type frame_to_tx: list of bytearrays @@ -534,16 +536,16 @@ class MeshRouter(): #print(frame_to_tx[0]) #print(frame_to_tx) frame_type = FRAME_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name - self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, + self.log.debug("[Modem] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, type=frame_type) # Set the TRANSMITTING flag before adding an object to the transmit queue - # TODO: This is not that nice, we could improve this somehow - TNC.transmitting = True + # TODO This is not that nice, we could improve this somehow + Modem.transmitting = True modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx]) # Wait while transmitting - while TNC.transmitting: + while Modem.transmitting: threading.Event().wait(0.01) diff --git a/tnc/modem.py b/modem/modem.py similarity index 97% rename from tnc/modem.py rename to modem/modem.py index a68d1f97..6df36fc5 100644 --- a/tnc/modem.py +++ b/modem/modem.py @@ -23,12 +23,12 @@ import numpy as np import sock import sounddevice as sd import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem from static import FRAME_TYPE import structlog import ujson as json import tci -# FIXME: used for def transmit_morse +# FIXME used for def transmit_morse # import cw from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \ AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE @@ -37,7 +37,7 @@ TESTMODE = False RXCHANNEL = "" TXCHANNEL = "" -TNC.transmitting = False +Modem.transmitting = False # Receive only specific modes to reduce CPU load RECEIVE_SIG0 = True @@ -287,7 +287,7 @@ class RF: ) fft_thread.start() - if TNC.enable_fsk: + if Modem.enable_fsk: audio_thread_fsk_ldpc0 = threading.Thread( target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True ) @@ -386,8 +386,8 @@ class RF: (self.dat0_datac1_buffer, RECEIVE_DATAC1), (self.dat0_datac3_buffer, RECEIVE_DATAC3), (self.dat0_datac4_buffer, RECEIVE_DATAC4), - (self.fsk_ldpc_buffer_0, TNC.enable_fsk), - (self.fsk_ldpc_buffer_1, TNC.enable_fsk), + (self.fsk_ldpc_buffer_0, Modem.enable_fsk), + (self.fsk_ldpc_buffer_1, Modem.enable_fsk), ]: if ( not (data_buffer.nbuffer + length_x) > data_buffer.size @@ -420,8 +420,8 @@ class RF: (self.dat0_datac1_buffer, RECEIVE_DATAC1), (self.dat0_datac3_buffer, RECEIVE_DATAC3), (self.dat0_datac4_buffer, RECEIVE_DATAC4), - (self.fsk_ldpc_buffer_0, TNC.enable_fsk), - (self.fsk_ldpc_buffer_1, TNC.enable_fsk), + (self.fsk_ldpc_buffer_0, Modem.enable_fsk), + (self.fsk_ldpc_buffer_1, Modem.enable_fsk), ]: if ( not (data_buffer.nbuffer + length_x) > data_buffer.size @@ -467,8 +467,8 @@ class RF: AudioParam.audio_record_file.writeframes(x) # Avoid decoding when transmitting to reduce CPU - # TODO: Overriding this for testing purposes - # if not TNC.transmitting: + # TODO Overriding this for testing purposes + # if not Modem.transmitting: length_x = len(x) # Avoid buffer overflow by filling only if buffer for # selected datachannel mode is not full @@ -478,21 +478,21 @@ class RF: (self.dat0_datac1_buffer, RECEIVE_DATAC1, 2), (self.dat0_datac3_buffer, RECEIVE_DATAC3, 3), (self.dat0_datac4_buffer, RECEIVE_DATAC4, 4), - (self.fsk_ldpc_buffer_0, TNC.enable_fsk, 5), - (self.fsk_ldpc_buffer_1, TNC.enable_fsk, 6), + (self.fsk_ldpc_buffer_0, Modem.enable_fsk, 5), + (self.fsk_ldpc_buffer_1, Modem.enable_fsk, 6), ]: if (audiobuffer.nbuffer + length_x) > audiobuffer.size: AudioParam.buffer_overflow_counter[index] += 1 elif receive: audiobuffer.push(x) - # end of "not TNC.transmitting" if block + # end of "not Modem.transmitting" if block if not self.modoutqueue or self.mod_out_locked: data_out48k = np.zeros(frames, dtype=np.int16) self.fft_data = x else: if not HamlibParam.ptt_state: - # TODO: Moved to this place for testing + # TODO Moved to this place for testing # Maybe we can avoid moments of silence before transmitting HamlibParam.ptt_state = self.radio.set_ptt(True) jsondata = {"ptt": "True"} @@ -541,12 +541,12 @@ class RF: else: return False - TNC.transmitting = True + Modem.transmitting = True # if we're transmitting FreeDATA signals, reset channel busy state ModemParam.channel_busy = False start_of_transmission = time.time() - # TODO: Moved ptt toggle some steps before audio is ready for testing + # 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 # HamlibParam.ptt_state = self.radio.set_ptt(True) # jsondata = {"ptt": "True"} @@ -729,7 +729,7 @@ class RF: self.mod_out_locked = True self.modem_transmit_queue.task_done() - TNC.transmitting = False + Modem.transmitting = False threading.Event().set() end_of_transmission = time.time() @@ -737,7 +737,7 @@ class RF: self.log.debug("[MDM] ON AIR TIME", time=transmission_time) def transmit_morse(self, repeats, repeat_delay, frames): - TNC.transmitting = True + Modem.transmitting = True # if we're transmitting FreeDATA signals, reset channel busy state ModemParam.channel_busy = False self.log.debug( @@ -800,7 +800,7 @@ class RF: self.mod_out_locked = True self.modem_transmit_queue.task_done() - TNC.transmitting = False + Modem.transmitting = False threading.Event().set() end_of_transmission = time.time() @@ -894,8 +894,8 @@ class RF: if nbytes == bytes_per_frame: print(bytes(bytes_out)) - # process commands only if TNC.listen = True - if TNC.listen: + # process commands only if Modem.listen = True + if Modem.listen: # ignore data channel opener frames for avoiding toggle states @@ -931,7 +931,7 @@ class RF: else: self.log.warning( "[MDM] [demod_audio] received frame but ignored processing", - listen=TNC.listen + listen=Modem.listen ) except Exception as e: self.log.warning("[MDM] [demod_audio] Stream not active anymore", e=e) @@ -1095,7 +1095,7 @@ class RF: """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 + # 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) @@ -1244,7 +1244,7 @@ class RF: threading.Event().wait(0.1) HamlibParam.hamlib_status = self.radio.get_status() threading.Event().wait(0.1) - if TNC.transmitting: + if Modem.transmitting: HamlibParam.alc = self.radio.get_alc() threading.Event().wait(0.1) # HamlibParam.hamlib_rf = self.radio.get_level() @@ -1295,7 +1295,7 @@ class RF: # Therefore we are setting it to 100 so it will be highlighted # Have to do this when we are not transmitting so our # own sending data will not affect this too much - if not TNC.transmitting: + if not Modem.transmitting: dfft[dfft > avg + 15] = 100 # Calculate audio dbfs @@ -1313,7 +1313,7 @@ class RF: raise ZeroDivisionError AudioParam.audio_dbfs = 20 * np.log10(rms / 32768) except Exception as e: - # FIXME: Disabled for cli cleanup + # FIXME Disabled for cli cleanup #self.log.warning( # "[MDM] fft calculation error - please check your audio setup", # e=e, @@ -1330,7 +1330,7 @@ class RF: # Reduce area where the busy detection is enabled # We want to have this in correlation with mode bandwidth - # TODO: This is not correctly and needs to be checked for correct maths + # TODO This is not correctly and needs to be checked for correct maths # dfftlist[0:1] = 10,15Hz # Bandwidth[Hz] / 10,15 # narrowband = 563Hz = 56 @@ -1354,12 +1354,12 @@ class RF: range_start = range[0] range_end = range[1] # define the area, we are detecting busy state - #dfft = dfft[120:176] if TNC.low_bandwidth_mode else dfft[65:231] + #dfft = dfft[120:176] if Modem.low_bandwidth_mode else dfft[65:231] slotdfft = dfft[range_start:range_end] # 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(slotdfft[slotdfft > avg + 15]) >= 200 and not TNC.transmitting: + if np.sum(slotdfft[slotdfft > avg + 15]) >= 200 and not Modem.transmitting: addDelay=True ModemParam.channel_busy_slot[slot] = True else: @@ -1456,7 +1456,7 @@ def get_bytes_per_frame(mode: int) -> int: :rtype: int """ freedv = open_codec2_instance(mode) - # TODO: add close session + # TODO add close session # get number of bytes per frame for mode return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) diff --git a/tnc/queues.py b/modem/queues.py similarity index 86% rename from tnc/queues.py rename to modem/queues.py index 14c907a9..befba860 100644 --- a/tnc/queues.py +++ b/modem/queues.py @@ -3,7 +3,7 @@ Hold queues used by more than one module to eliminate cyclic imports. """ import queue import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem DATA_QUEUE_TRANSMIT = queue.Queue() DATA_QUEUE_RECEIVED = queue.Queue() diff --git a/tnc/rigctld.py b/modem/rigctld.py similarity index 98% rename from tnc/rigctld.py rename to modem/rigctld.py index 27bce855..ced1b7fb 100644 --- a/tnc/rigctld.py +++ b/modem/rigctld.py @@ -10,7 +10,7 @@ import time import structlog import threading import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam class radio: diff --git a/tnc/rigdummy.py b/modem/rigdummy.py similarity index 100% rename from tnc/rigdummy.py rename to modem/rigdummy.py diff --git a/tnc/selftest.py b/modem/selftest.py similarity index 88% rename from tnc/selftest.py rename to modem/selftest.py index 5cfec42a..d3f4b1e9 100644 --- a/tnc/selftest.py +++ b/modem/selftest.py @@ -1,5 +1,5 @@ """ -simple TNC self tests +simple Modem self tests """ # -*- coding: utf-8 -*- @@ -7,7 +7,6 @@ simple TNC self tests # pylint: disable=import-outside-toplevel, attribute-defined-outside-init import sys import structlog -from static import ARQ, Audio, Beacon, Channel, Daemon, Hamlib, Modem, Station, TCI, TNC log = structlog.get_logger("selftest") @@ -16,9 +15,9 @@ class TEST(): def __init__(self): log.info("[selftest] running self tests...") if self.run_tests(): - log.info("[selftest] passed -> starting TNC") + log.info("[selftest] passed -> starting Modem") else: - log.error("[selftest] failed -> closing TNC") + log.error("[selftest] failed -> closing Modem") sys.exit(0) def run_tests(self): diff --git a/tnc/sock.py b/modem/sock.py similarity index 69% rename from tnc/sock.py rename to modem/sock.py index 94ac8d1e..a1da13c7 100644 --- a/tnc/sock.py +++ b/modem/sock.py @@ -27,7 +27,7 @@ import time import wave import helpers import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam import structlog from random import randrange import ujson as json @@ -66,10 +66,10 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): """ tempdata = b"" while self.connection_alive and not CLOSE_SIGNAL: - # send tnc state as network stream + # send modem state as network stream # check server port against daemon port and send corresponding data - if self.server.server_address[1] == TNC.port and not Daemon.tncstarted: - data = send_tnc_state() + if self.server.server_address[1] == Modem.port and not Daemon.modemstarted: + data = send_modem_state() if data != tempdata: tempdata = data SOCKET_QUEUE.put(data) @@ -92,7 +92,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): 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. + # 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: @@ -126,8 +126,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): # iterate thorugh data list for commands in data: - if self.server.server_address[1] == TNC.port: - self.process_tnc_commands(commands) + if self.server.server_address[1] == Modem.port: + self.process_modem_commands(commands) else: self.process_daemon_commands(commands) @@ -191,10 +191,10 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): e=e, ) - # ------------------------ TNC COMMANDS - def process_tnc_commands(self, data): + # ------------------------ Modem COMMANDS + def process_modem_commands(self, data): """ - process tnc commands + process modem commands Args: data: @@ -202,7 +202,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): Returns: """ - log = structlog.get_logger("process_tnc_commands") + log = structlog.get_logger("process_modem_commands") # we need to do some error handling in case of socket timeout or decoding issue try: @@ -210,207 +210,207 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): received_json = json.loads(data) log.debug("[SCK] CMD", command=received_json) - # ENABLE TNC LISTENING STATE + # ENABLE Modem LISTENING STATE if received_json["type"] == "set" and received_json["command"] == "listen": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_listen(None, received_json) + ThreadedTCPRequestHandler.modem_set_listen(None, received_json) else: - self.tnc_set_listen(received_json) + self.modem_set_listen(received_json) # START STOP AUDIO RECORDING if received_json["type"] == "set" and received_json["command"] == "record_audio": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_record_audio(None, received_json) + ThreadedTCPRequestHandler.modem_set_record_audio(None, received_json) else: - self.tnc_set_record_audio(received_json) + self.modem_set_record_audio(received_json) # SET ENABLE/DISABLE RESPOND TO CALL if received_json["type"] == "set" and received_json["command"] == "respond_to_call": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_respond_to_call(None, received_json) + ThreadedTCPRequestHandler.modem_set_respond_to_call(None, received_json) else: - self.tnc_set_respond_to_call(received_json) + self.modem_set_respond_to_call(received_json) # SET ENABLE RESPOND TO CQ if received_json["type"] == "set" and received_json["command"] == "respond_to_cq": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_record_audio(None, received_json) + ThreadedTCPRequestHandler.modem_set_record_audio(None, received_json) else: - self.tnc_set_record_audio(received_json) + self.modem_set_record_audio(received_json) # SET TX AUDIO LEVEL if received_json["type"] == "set" and received_json["command"] == "tx_audio_level": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_tx_audio_level(None, received_json) + ThreadedTCPRequestHandler.modem_set_tx_audio_level(None, received_json) else: - self.tnc_set_tx_audio_level(received_json) + self.modem_set_tx_audio_level(received_json) # TRANSMIT TEST FRAME if received_json["type"] == "set" and received_json["command"] == "send_test_frame": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_send_test_frame(None, received_json) - elif TNC.tnc_state in ['busy']: + ThreadedTCPRequestHandler.modem_set_send_test_frame(None, received_json) + elif Modem.modem_state in ['busy']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_set_send_test_frame(received_json) + self.modem_set_send_test_frame(received_json) # TRANSMIT FEC FRAME if received_json["type"] == "fec" and received_json["command"] == "transmit": if TESTMODE: - ThreadedTCPRequestHandler.tnc_fec_transmit(None, received_json) + ThreadedTCPRequestHandler.modem_fec_transmit(None, received_json) else: - self.tnc_fec_transmit(received_json) + self.modem_fec_transmit(received_json) # TRANSMIT IS WRITING FRAME if received_json["type"] == "fec" and received_json["command"] == "transmit_is_writing": if TESTMODE: - ThreadedTCPRequestHandler.tnc_fec_is_writing(None, received_json) - elif TNC.tnc_state in ['busy']: + ThreadedTCPRequestHandler.modem_fec_is_writing(None, received_json) + elif Modem.modem_state in ['busy']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_fec_is_writing(received_json) + self.modem_fec_is_writing(received_json) # CQ CQ CQ if received_json["command"] == "cqcqcq": if TESTMODE: - ThreadedTCPRequestHandler.tnc_cqcqcq(None, received_json) - elif TNC.tnc_state in ['BUSY']: + ThreadedTCPRequestHandler.modem_cqcqcq(None, received_json) + elif Modem.modem_state in ['BUSY']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_cqcqcq(received_json) + self.modem_cqcqcq(received_json) # START_BEACON if received_json["command"] == "start_beacon": if TESTMODE: - ThreadedTCPRequestHandler.tnc_start_beacon(None, received_json) + ThreadedTCPRequestHandler.modem_start_beacon(None, received_json) else: - self.tnc_start_beacon(received_json) + self.modem_start_beacon(received_json) # STOP_BEACON if received_json["command"] == "stop_beacon": if TESTMODE: - ThreadedTCPRequestHandler.tnc_stop_beacon(None, received_json) + ThreadedTCPRequestHandler.modem_stop_beacon(None, received_json) else: - self.tnc_stop_beacon(received_json) + self.modem_stop_beacon(received_json) # PING if received_json["type"] == "ping" and received_json["command"] == "ping": if TESTMODE: - ThreadedTCPRequestHandler.tnc_ping_ping(None, received_json) - elif TNC.tnc_state in ['BUSY']: + ThreadedTCPRequestHandler.modem_ping_ping(None, received_json) + elif Modem.modem_state in ['BUSY']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_ping_ping(received_json) + self.modem_ping_ping(received_json) # CONNECT if received_json["type"] == "arq" and received_json["command"] == "connect": if TESTMODE: - ThreadedTCPRequestHandler.tnc_arq_connect(None, received_json) - elif TNC.tnc_state in ['BUSY']: + ThreadedTCPRequestHandler.modem_arq_connect(None, received_json) + elif Modem.modem_state in ['BUSY']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_arq_connect(received_json) + self.modem_arq_connect(received_json) # DISCONNECT if received_json["type"] == "arq" and received_json["command"] == "disconnect": if TESTMODE: - ThreadedTCPRequestHandler.tnc_arq_disconnect(None, received_json) + ThreadedTCPRequestHandler.modem_arq_disconnect(None, received_json) else: - self.tnc_arq_disconnect(received_json) + self.modem_arq_disconnect(received_json) # TRANSMIT RAW DATA if received_json["type"] == "arq" and received_json["command"] == "send_raw": if TESTMODE: - ThreadedTCPRequestHandler.tnc_arq_send_raw(None, received_json) - elif TNC.tnc_state in ['busy']: + ThreadedTCPRequestHandler.modem_arq_send_raw(None, received_json) + elif Modem.modem_state in ['busy']: log.warning( "[SCK] Dropping command", - e="tnc state", - state=TNC.tnc_state, + e="modem state", + state=Modem.modem_state, command=received_json, ) else: - self.tnc_arq_send_raw(received_json) + self.modem_arq_send_raw(received_json) # STOP TRANSMISSION if received_json["type"] == "arq" and received_json["command"] == "stop_transmission": if TESTMODE: - ThreadedTCPRequestHandler.tnc_arq_stop_transmission(None, received_json) + ThreadedTCPRequestHandler.modem_arq_stop_transmission(None, received_json) else: - self.tnc_arq_stop_transmission(received_json) + self.modem_arq_stop_transmission(received_json) # GET RX BUFFER if received_json["type"] == "get" and received_json["command"] == "rx_buffer": if TESTMODE: - ThreadedTCPRequestHandler.tnc_get_rx_buffer(None, received_json) + ThreadedTCPRequestHandler.modem_get_rx_buffer(None, received_json) else: - self.tnc_get_rx_buffer(received_json) + self.modem_get_rx_buffer(received_json) # DELETE RX BUFFER if received_json["type"] == "set" and received_json["command"] == "del_rx_buffer": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_del_rx_buffer(None, received_json) + ThreadedTCPRequestHandler.modem_set_del_rx_buffer(None, received_json) else: - self.tnc_set_del_rx_buffer(received_json) + self.modem_set_del_rx_buffer(received_json) # SET FREQUENCY if received_json["type"] == "set" and received_json["command"] == "frequency": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_frequency(None, received_json) + ThreadedTCPRequestHandler.modem_set_frequency(None, received_json) else: - self.tnc_set_frequency(received_json) + self.modem_set_frequency(received_json) # SET MODE if received_json["type"] == "set" and received_json["command"] == "mode": if TESTMODE: - ThreadedTCPRequestHandler.tnc_set_mode(None, received_json) + ThreadedTCPRequestHandler.modem_set_mode(None, received_json) else: - self.tnc_set_mode(received_json) + self.modem_set_mode(received_json) # GET ROUTING TABLE if received_json["type"] == "get" and received_json["command"] == "routing_table": - self.tnc_get_mesh_routing_table(received_json) + self.modem_get_mesh_routing_table(received_json) # -------------- MESH ---------------- # # MESH PING if received_json["type"] == "mesh" and received_json["command"] == "ping": - self.tnc_mesh_ping(received_json) + self.modem_mesh_ping(received_json) except Exception as err: log.error("[SCK] JSON decoding error", e=err) - def tnc_set_listen(self, received_json): + def modem_set_listen(self, received_json): try: - TNC.listen = received_json["state"] in ['true', 'True', True, "ON", "on"] + Modem.listen = received_json["state"] in ['true', 'True', True, "ON", "on"] command_response("listen", True) - # if tnc is connected, force disconnect when TNC.listen == False - if not TNC.listen and ARQ.arq_session_state not in ["disconnecting", "disconnected", "failed"]: + # if modem is connected, force disconnect when Modem.listen == False + if not Modem.listen and ARQ.arq_session_state not in ["disconnecting", "disconnected", "failed"]: DATA_QUEUE_TRANSMIT.put(["DISCONNECT"]) # set early disconnecting state so we can interrupt connection attempts ARQ.arq_session_state = "disconnecting" @@ -424,11 +424,11 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] CQ command execution error", e=err, command=received_json ) - def tnc_set_record_audio(self, received_json): + def modem_set_record_audio(self, received_json): try: if not AudioParam.audio_record: AudioParam.audio_record_file = wave.open(f"{int(time.time())}_audio_recording.wav", 'w') - AudioParam.audio_record_file.setnchannels(1) + AudioParam.audio_record_file.semodemhannels(1) AudioParam.audio_record_file.setsampwidth(2) AudioParam.audio_record_file.setframerate(8000) AudioParam.audio_record = True @@ -444,9 +444,9 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] CQ command execution error", e=err, command=received_json ) - def tnc_set_respond_to_call(self, received_json): + def modem_set_respond_to_call(self, received_json): try: - TNC.respond_to_call = received_json["state"] in ['true', 'True', True] + Modem.respond_to_call = received_json["state"] in ['true', 'True', True] command_response("respond_to_call", True) except Exception as err: @@ -455,9 +455,9 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] CQ command execution error", e=err, command=received_json ) - def tnc_set_respond_to_cq(self, received_json): + def modem_set_respond_to_cq(self, received_json): try: - TNC.respond_to_cq = received_json["state"] in ['true', 'True', True] + Modem.respond_to_cq = received_json["state"] in ['true', 'True', True] command_response("respond_to_cq", True) except Exception as err: @@ -466,7 +466,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] CQ command execution error", e=err, command=received_json ) - def tnc_set_tx_audio_level(self, received_json): + def modem_set_tx_audio_level(self, received_json): try: AudioParam.tx_audio_level = int(received_json["value"]) command_response("tx_audio_level", True) @@ -479,7 +479,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_set_send_test_frame(self, received_json): + def modem_set_send_test_frame(self, received_json): try: DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"]) command_response("send_test_frame", True) @@ -491,7 +491,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_fec_transmit(self, received_json): + def modem_fec_transmit(self, received_json): try: mode = received_json["mode"] wakeup = received_json["wakeup"] @@ -519,7 +519,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_fec_is_writing(self, received_json): + def modem_fec_is_writing(self, received_json): try: mycallsign = received_json["mycallsign"] DATA_QUEUE_TRANSMIT.put(["FEC_IS_WRITING", mycallsign]) @@ -532,7 +532,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_cqcqcq(self, received_json): + def modem_cqcqcq(self, received_json): try: DATA_QUEUE_TRANSMIT.put(["CQ"]) command_response("cqcqcq", True) @@ -543,7 +543,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] CQ command execution error", e=err, command=received_json ) - def tnc_start_beacon(self, received_json): + def modem_start_beacon(self, received_json): try: Beacon.beacon_state = True interval = int(received_json["parameter"]) @@ -557,7 +557,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_stop_beacon(self, received_json): + def modem_stop_beacon(self, received_json): try: log.warning("[SCK] Stopping beacon!") Beacon.beacon_state = False @@ -572,7 +572,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): ) - def tnc_mesh_ping(self, received_json): + def modem_mesh_ping(self, received_json): # send ping frame and wait for ACK try: dxcallsign = received_json["dxcallsign"] @@ -585,7 +585,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign) - # check if specific callsign is set with different SSID than the TNC is initialized + # check if specific callsign is set with different SSID than the Modem is initialized try: mycallsign = received_json["mycallsign"] mycallsign = helpers.callsign_to_bytes(mycallsign) @@ -605,7 +605,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] PING command execution error", e=err, command=received_json ) - def tnc_ping_ping(self, received_json): + def modem_ping_ping(self, received_json): # send ping frame and wait for ACK try: @@ -619,7 +619,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign) - # check if specific callsign is set with different SSID than the TNC is initialized + # check if specific callsign is set with different SSID than the Modem is initialized try: mycallsign = received_json["mycallsign"] mycallsign = helpers.callsign_to_bytes(mycallsign) @@ -639,7 +639,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] PING command execution error", e=err, command=received_json ) - def tnc_arq_connect(self, received_json): + def modem_arq_connect(self, received_json): # pause our beacon first Beacon.beacon_pause = True @@ -653,7 +653,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): dxcallsign = received_json["dxcallsign"] - # check if specific callsign is set with different SSID than the TNC is initialized + # check if specific callsign is set with different SSID than the Modem is initialized try: mycallsign = received_json["mycallsign"] mycallsign = helpers.callsign_to_bytes(mycallsign) @@ -697,7 +697,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): # allow beacon transmission again Beacon.beacon_pause = False - def tnc_arq_disconnect(self, received_json): + def modem_arq_disconnect(self, received_json): try: if ARQ.arq_session_state not in ["disconnecting", "disconnected", "failed"]: DATA_QUEUE_TRANSMIT.put(["DISCONNECT"]) @@ -716,13 +716,13 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_arq_send_raw(self, received_json): + def modem_arq_send_raw(self, received_json): Beacon.beacon_pause = True # wait some random time helpers.wait(randrange(5, 25, 5) / 10.0) - # TODO: carefully test this + # TODO carefully test this # avoid sending data while we are receiving codec2 signalling data interrupt_time = time.time() + 5 while ModemParam.is_codec2_traffic and time.time() < interrupt_time: @@ -754,7 +754,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): base64data = received_json["parameter"][0]["data"] - # check if specific callsign is set with different SSID than the TNC is initialized + # check if specific callsign is set with different SSID than the Modem is initialized try: mycallsign = received_json["parameter"][0]["mycallsign"] mycallsign = helpers.callsign_to_bytes(mycallsign) @@ -800,12 +800,12 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_arq_stop_transmission(self, received_json): + def modem_arq_stop_transmission(self, received_json): try: - if TNC.tnc_state == "BUSY" or ARQ.arq_state: + if Modem.modem_state == "BUSY" or ARQ.arq_state: DATA_QUEUE_TRANSMIT.put(["STOP"]) log.warning("[SCK] Stopping transmission!") - TNC.tnc_state = "IDLE" + Modem.modem_state = "IDLE" ARQ.arq_state = False command_response("stop_transmission", True) except Exception as err: @@ -814,35 +814,34 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): "[SCK] STOP command execution error", e=err, command=received_json ) - def tnc_get_mesh_routing_table(self, received_json): + def modem_get_mesh_routing_table(self, received_json): try: - if not RX_BUFFER.empty(): - output = { - "command": "routing_table", - "routes": [], - } + output = { + "command": "routing_table", + "routes": [], + } - for _, route in enumerate(MeshParam.routing_table): - if MeshParam.routing_table[_][0].hex() == helpers.get_crc_24(b"direct").hex(): - router = "direct" - else: - router = MeshParam.routing_table[_][0].hex() - output["routes"].append( - { - "dxcall": MeshParam.routing_table[_][0].hex(), - "router": router, - "hops": MeshParam.routing_table[_][2], - "snr": MeshParam.routing_table[_][3], - "score": MeshParam.routing_table[_][4], - "timestamp": MeshParam.routing_table[_][5], - } - ) + for _, route in enumerate(MeshParam.routing_table): + if MeshParam.routing_table[_][0].hex() == helpers.get_crc_24(b"direct").hex(): + router = "direct" + else: + router = MeshParam.routing_table[_][0].hex() + output["routes"].append( + { + "dxcall": MeshParam.routing_table[_][0].hex(), + "router": router, + "hops": MeshParam.routing_table[_][2], + "snr": MeshParam.routing_table[_][3], + "score": MeshParam.routing_table[_][4], + "timestamp": MeshParam.routing_table[_][5], + } + ) - jsondata = json.dumps(output) - # self.request.sendall(bytes(jsondata, encoding)) - SOCKET_QUEUE.put(jsondata) - command_response("routing_table", True) + jsondata = json.dumps(output) + # self.request.sendall(bytes(jsondata, encoding)) + SOCKET_QUEUE.put(jsondata) + command_response("routing_table", True) except Exception as err: command_response("routing_table", False) @@ -852,29 +851,75 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_get_rx_buffer(self, received_json): + def modem_get_rx_buffer(self, received_json): try: if not RX_BUFFER.empty(): - output = { - "command": "rx_buffer", - "data-array": [], - } + # TODO REMOVE DEPRECATED MESSAGES + #output = { + # "command": "rx_buffer", + # "data-array": [], + #}# + #for _buffer_length in range(RX_BUFFER.qsize()): + # base64_data = RX_BUFFER.queue[_buffer_length][4] + # output["data-array"].append( + # { + # "uuid": RX_BUFFER.queue[_buffer_length][0], + # "timestamp": RX_BUFFER.queue[_buffer_length][1], + # "dxcallsign": str(RX_BUFFER.queue[_buffer_length][2], "utf-8"), + # "dxgrid": str(RX_BUFFER.queue[_buffer_length][3], "utf-8"), + # "data": base64_data, + # } + # ) + #jsondata = json.dumps(output) + ## self.request.sendall(bytes(jsondata, encoding)) + #SOCKET_QUEUE.put(jsondata) + #command_response("rx_buffer", True) + + + # REQUEST REQUEST RX BUFFER AGAIN + # NEW BEHAVIOUR IS, PUSHING DATA TO NETWORK LIKE WE RECEIVED IT + # RX_BUFFER[0] = transmission uuid + # RX_BUFFER[1] = timestamp + # RX_BUFFER[2] = dxcallsign + # RX_BUFFER[3] = dxgrid + # RX_BUFFER[4] = data + # RX_BUFFER[5] = hmac signed + # RX_BUFFER[6] = compression factor + # RX_BUFFER[7] = bytes per minute + # RX_BUFFER[8] = duration + # RX_BUFFER[9] = self.frame_nack_counter + # RX_BUFFER[10] = speed list stats for _buffer_length in range(RX_BUFFER.qsize()): - base64_data = RX_BUFFER.queue[_buffer_length][4] - output["data-array"].append( - { - "uuid": RX_BUFFER.queue[_buffer_length][0], - "timestamp": RX_BUFFER.queue[_buffer_length][1], - "dxcallsign": str(RX_BUFFER.queue[_buffer_length][2], "utf-8"), - "dxgrid": str(RX_BUFFER.queue[_buffer_length][3], "utf-8"), - "data": base64_data, - } - ) - jsondata = json.dumps(output) - # self.request.sendall(bytes(jsondata, encoding)) - SOCKET_QUEUE.put(jsondata) - command_response("rx_buffer", True) + output = { + "freedata" : "modem-message", + "arq" : "transmission", + "status" : "received", + "uuid" : RX_BUFFER.queue[_buffer_length][0], + "percent" : 100, + "bytesperminute" : RX_BUFFER.queue[_buffer_length][7], + "compression" : RX_BUFFER.queue[_buffer_length][6], + "timestamp" : RX_BUFFER.queue[_buffer_length][1], + "finished" : 0, + "mycallsign" : str(Station.mycallsign, "UTF-8"), + "dxcallsign" : str(RX_BUFFER.queue[_buffer_length][2], "utf-8"), + "dxgrid" : str(RX_BUFFER.queue[_buffer_length][3], "utf-8"), + "data" : RX_BUFFER.queue[_buffer_length][4], + "irs" : RX_BUFFER.queue[_buffer_length][5], + "hmac_signed" : "False", + "duration" : RX_BUFFER.queue[_buffer_length][8], + "nacks" : RX_BUFFER.queue[_buffer_length][9], + "speed_list" : RX_BUFFER.queue[_buffer_length][10] + } + + jsondata = json.dumps(output) + SOCKET_QUEUE.put(jsondata) + print(jsondata) + + command_response("rx_buffer", True) + + + except Exception as err: command_response("rx_buffer", False) @@ -884,7 +929,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_set_del_rx_buffer(self, received_json): + def modem_set_del_rx_buffer(self, received_json): try: RX_BUFFER.queue.clear() command_response("del_rx_buffer", True) @@ -896,7 +941,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_set_mode(self, received_json): + def modem_set_mode(self, received_json): try: RIGCTLD_COMMAND_QUEUE.put(["set_mode", received_json["mode"]]) command_response("set_mode", True) @@ -908,7 +953,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) - def tnc_set_frequency(self, received_json): + def modem_set_frequency(self, received_json): try: RIGCTLD_COMMAND_QUEUE.put(["set_frequency", received_json["frequency"]]) command_response("set_frequency", True) @@ -945,16 +990,22 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): if ( received_json["type"] == "set" - and received_json["command"] == "start_tnc" - and not Daemon.tncstarted + and received_json["command"] == "start_modem" + and not Daemon.modemstarted ): - self.daemon_start_tnc(received_json) + self.daemon_start_modem(received_json) if received_json["type"] == "get" and received_json["command"] == "test_hamlib": self.daemon_test_hamlib(received_json) - if received_json["type"] == "set" and received_json["command"] == "stop_tnc": - self.daemon_stop_tnc(received_json) + if received_json["type"] == "set" and received_json["command"] == "stop_modem": + self.daemon_stop_modem(received_json) + + if received_json["type"] == "set" and received_json["command"] == "start_rigctld" and not Daemon.rigctldstarted: + self.daemon_start_rigctld(received_json) + + if received_json["type"] == "set" and received_json["command"] == "stop_rigctld": + self.daemon_stop_rigctld(received_json) def daemon_set_mycallsign(self, received_json): try: @@ -996,7 +1047,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command_response("mygrid", False) log.warning("[SCK] command execution error", e=err, command=received_json) - def daemon_start_tnc(self, received_json): + def daemon_start_modem(self, received_json): try: startparam = received_json["parameter"][0] @@ -1036,13 +1087,13 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): # print some debugging parameters for item in startparam: log.debug( - f"[SCK] TNC Startup Config : {item}", + f"[SCK] Modem Startup Config : {item}", value=startparam[item], ) DAEMON_QUEUE.put( [ - "STARTTNC", + "STARTModem", mycall, mygrid, rx_audio, @@ -1069,23 +1120,23 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): enable_mesh ] ) - command_response("start_tnc", True) + command_response("start_modem", True) except Exception as err: - command_response("start_tnc", False) + command_response("start_modem", False) log.warning("[SCK] command execution error", e=err, command=received_json) - def daemon_stop_tnc(self, received_json): + def daemon_stop_modem(self, received_json): try: - Daemon.tncprocess.kill() + Daemon.modemprocess.kill() # unregister process from atexit to avoid process zombies - atexit.unregister(Daemon.tncprocess.kill) + atexit.unregister(Daemon.modemprocess.kill) - log.warning("[SCK] Stopping TNC") - Daemon.tncstarted = False - command_response("stop_tnc", True) + log.warning("[SCK] Stopping Modem") + Daemon.modemstarted = False + command_response("stop_modem", True) except Exception as err: - command_response("stop_tnc", False) + command_response("stop_modem", False) log.warning("[SCK] command execution error", e=err, command=received_json) def daemon_test_hamlib(self, received_json): @@ -1107,6 +1158,89 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command_response("test_hamlib", False) log.warning("[SCK] command execution error", e=err, command=received_json) + def daemon_start_rigctld(self, received_json): + """ + hamlib_deviceid: settings.hamlib_deviceid, + hamlib_deviceport: settings.hamlib_deviceport, + hamlib_stop_bits: settings.hamlib_stop_bits, + hamlib_data_bits: settings.hamlib_data_bits, + hamlib_handshake: settings.hamlib_handshake, + hamlib_serialspeed: settings.hamlib_serialspeed, + hamlib_dtrstate: settings.hamlib_dtrstate, + hamlib_pttprotocol: settings.hamlib_pttprotocol, + hamlib_ptt_port: settings.hamlib_ptt_port, + hamlib_dcd: settings.hamlib_dcd, + hamlbib_serialspeed_ptt: settings.hamlib_serialspeed, + hamlib_rigctld_port: settings.hamlib_rigctld_port, + hamlib_rigctld_ip: settings.hamlib_rigctld_ip, + hamlib_rigctld_path: settings.hamlib_rigctld_path, + hamlib_rigctld_server_port: settings.hamlib_rigctld_server_port, + hamlib_rigctld_custom_args: settings.hamlib_rigctld_custom_args + """ + try: + + hamlib_deviceid = str(received_json["parameter"][0]["hamlib_deviceid"]) + hamlib_deviceport = str(received_json["parameter"][0]["hamlib_deviceport"]) + hamlib_stop_bits = str(received_json["parameter"][0]["hamlib_stop_bits"]) + hamlib_data_bits = str(received_json["parameter"][0]["hamlib_data_bits"]) + hamlib_handshake = str(received_json["parameter"][0]["hamlib_handshake"]) + hamlib_serialspeed = str(received_json["parameter"][0]["hamlib_serialspeed"]) + hamlib_dtrstate = str(received_json["parameter"][0]["hamlib_dtrstate"]) + hamlib_pttprotocol = str(received_json["parameter"][0]["hamlib_pttprotocol"]) + hamlib_ptt_port = str(received_json["parameter"][0]["hamlib_ptt_port"]) + hamlib_dcd = str(received_json["parameter"][0]["hamlib_dcd"]) + hamlbib_serialspeed_ptt = str(received_json["parameter"][0]["hamlib_serialspeed"]) + hamlib_rigctld_port = str(received_json["parameter"][0]["hamlib_rigctld_port"]) + hamlib_rigctld_ip = str(received_json["parameter"][0]["hamlib_rigctld_ip"]) + hamlib_rigctld_path = str(received_json["parameter"][0]["hamlib_rigctld_path"]) + hamlib_rigctld_server_port = str(received_json["parameter"][0]["hamlib_rigctld_server_port"]) + hamlib_rigctld_custom_args = str(received_json["parameter"][0]["hamlib_rigctld_custom_args"]) + + DAEMON_QUEUE.put( + [ + "START_RIGCTLD", + hamlib_deviceid, + hamlib_deviceport, + hamlib_stop_bits, + hamlib_data_bits, + hamlib_handshake, + hamlib_serialspeed, + hamlib_dtrstate, + hamlib_pttprotocol, + hamlib_ptt_port, + hamlib_dcd, + hamlbib_serialspeed_ptt, + hamlib_rigctld_port, + hamlib_rigctld_ip, + hamlib_rigctld_path, + hamlib_rigctld_server_port, + hamlib_rigctld_custom_args + ] + ) + command_response("start_rigctld", True) + except Exception as err: + command_response("start_rigctld", False) + log.warning("[SCK] command execution error", e=err, command=received_json) + + + def daemon_stop_rigctld(self, received_json): + + try: + log.warning("[SCK] Stopping rigctld") + + if Daemon.rigctldstarted: + Daemon.rigctldprocess.kill() + # unregister process from atexit to avoid process zombies + atexit.unregister(Daemon.rigctldprocess.kill) + + Daemon.rigctldstarted = False + command_response("stop_rigctld", True) + except Exception as err: + command_response("stop_modem", False) + log.warning("[SCK] command execution error", e=err, command=received_json) + + + def send_daemon_state(): """ @@ -1114,41 +1248,77 @@ def send_daemon_state(): """ log = structlog.get_logger("send_daemon_state") + # we need to do some process checking for providing the correct state + # at least we are checking the returncode of rigctld + # None state means, the process is still running + try: + retcode_rigctld = Daemon.rigctldprocess + if retcode_rigctld in [None, "None"]: + Daemon.rigctldstarted = False + # This is a blocking code .... + # output, errs = Daemon.rigctldprocess.communicate() + # print(f"rigctld out: {output}") + # print(f"rigctld err: {errs}") + else: + # print(f"rigctld closed with code: {retcode_rigctld}") + Daemon.rigctldstarted = True + + + retcode_modem = Daemon.modemprocess + if retcode_modem in [None, "None"]: + Daemon.modemstarted = False + # This is a blocking code .... + # output, errs = Daemon.modemprocess.communicate() + # print(f"modem out: {output}") + # print(f"modem err: {errs}") + else: + # print(f"modem closed with code: {retcode_modem}") + Daemon.modemstarted = True + + except Exception as err: + log.warning("[DMN] error", e=err) + try: python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}" output = { "command": "daemon_state", "daemon_state": [], + "rigctld_state": [], "python_version": str(python_version), "input_devices": AudioParam.audio_input_devices, "output_devices": AudioParam.audio_output_devices, "serial_devices": Daemon.serial_devices, # 'cpu': str(psutil.cpu_percent()), # 'ram': str(psutil.virtual_memory().percent), - "version": "0.1", + "version": Modem.version, } - if Daemon.tncstarted: + if Daemon.modemstarted: output["daemon_state"].append({"status": "running"}) else: output["daemon_state"].append({"status": "stopped"}) + if Daemon.rigctldstarted: + output["rigctld_state"].append({"status": "running"}) + else: + output["rigctld_state"].append({"status": "stopped"}) + return json.dumps(output) except Exception as err: log.warning("[SCK] error", e=err) return None -def send_tnc_state(): +def send_modem_state(): """ - send the tnc state to network + send the modem state to network """ encoding = "utf-8" output = { - "command": "tnc_state", + "command": "modem_state", "ptt_state": str(HamlibParam.ptt_state), - "tnc_state": str(TNC.tnc_state), + "modem_state": str(Modem.modem_state), "arq_state": str(ARQ.arq_state), "arq_session": str(ARQ.arq_session), "arq_session_state": str(ARQ.arq_session_state), @@ -1173,6 +1343,7 @@ def send_tnc_state(): "arq_bytes_per_minute": str(ARQ.bytes_per_minute), "arq_bytes_per_minute_burst": str(ARQ.bytes_per_minute_burst), "arq_seconds_until_finish": str(ARQ.arq_seconds_until_finish), + "arq_seconds_until_timeout": str(ARQ.arq_seconds_until_timeout), "arq_compression_factor": str(ARQ.arq_compression_factor), "arq_transmission_percent": str(ARQ.arq_transmission_percent), "speed_list": ARQ.speed_list, @@ -1186,12 +1357,13 @@ def send_tnc_state(): "dxcallsign": str(Station.dxcallsign, encoding), "dxgrid": str(Station.dxgrid, encoding), "hamlib_status": HamlibParam.hamlib_status, - "listen": str(TNC.listen), + "listen": str(Modem.listen), "audio_recording": str(AudioParam.audio_record), + } # add heard stations to heard stations object - for heard in TNC.heard_stations: + for heard in Modem.heard_stations: output["stations"].append( { "dxcallsign": str(heard[0], encoding), diff --git a/tnc/static.py b/modem/static.py similarity index 52% rename from tnc/static.py rename to modem/static.py index 6e6d3def..13ca89cb 100644 --- a/tnc/static.py +++ b/modem/static.py @@ -26,6 +26,7 @@ class ARQ: arq_bits_per_second_burst: int = 0 arq_bits_per_second: int = 0 arq_seconds_until_finish: int = 0 + arq_seconds_until_timeout: int = 0 rx_buffer_size: int = 16 rx_frame_buffer: bytes = b"" rx_burst_buffer = [] @@ -35,7 +36,8 @@ class ARQ: # ARQ PROTOCOL VERSION # v.5 - signalling frame uses datac0 # v.6 - signalling frame uses datac13 - arq_protocol_version: int = 6 + # v.7 - adjusting ARQ timeouts, not done yet + arq_protocol_version: int = 7 total_bytes: int = 0 speed_list = [] # set save to folder state for allowing downloading files to local file system @@ -71,8 +73,10 @@ class Channel: @dataclass class Daemon: - tncprocess: subprocess.Popen - tncstarted: bool = False + modemprocess: subprocess.Popen + rigctldprocess: subprocess.Popen + modemstarted: bool = False + rigctldstarted: bool = False port: int = 3001 serial_devices = [] @@ -129,12 +133,12 @@ class TCIParam: port: int = '9000' @dataclass -class TNC: - version = "0.10.3-alpha.1-hmac-exp4" +class Modem: + version = "0.11.0-alpha.1-vuejs" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds - tnc_state: str = "IDLE" + modem_state: str = "IDLE" enable_explorer = False enable_stats = False transmitting: bool = False @@ -183,138 +187,4 @@ class FRAME_TYPE(Enum): FEC = 251 FEC_WAKEUP = 252 IDENT = 254 - TEST_FRAME = 255 - -# --------------------------------------------------------------- -# DON'T USE THESE SETTINGS ANYMORE -# --------------------------------------------------------------- -# Fixme: REMOVE THESE OLD SETTINGS! -# REASON: For some reason ctests are failing when using dataclasses. -# We need to figure out whats happening and why the tests are failing. - - -CHANNEL_BUSY_SLOT = [False] * 5 - - -ENABLE_EXPLORER = False -ENABLE_STATS = False - - -# DAEMON -DAEMONPORT: int = 3001 -TNCSTARTED: bool = False -TNCPROCESS: subprocess.Popen - -# Operator Defaults -MYCALLSIGN: bytes = b"AA0AA" -MYCALLSIGN_CRC: bytes = b"A" - -DXCALLSIGN: bytes = b"ZZ9YY" -DXCALLSIGN_CRC: bytes = b"A" - -MYGRID: bytes = b"" -DXGRID: bytes = b"" - -SSID_LIST: list = [] # ssid list we are responding to - -LOW_BANDWIDTH_MODE: bool = False -# --------------------------------- - -# Server Defaults -HOST: str = "0.0.0.0" -PORT: int = 3000 -SOCKET_TIMEOUT: int = 1 # seconds -# --------------------------------- -SERIAL_DEVICES: list = [] -# --------------------------------- -LISTEN: bool = True -PTT_STATE: bool = False -TRANSMITTING: bool = False - -HAMLIB_RADIOCONTROL: str = "disabled" -HAMLIB_RIGCTLD_IP: str = "127.0.0.1" -HAMLIB_RIGCTLD_PORT: str = "4532" - -HAMLIB_STATUS: str = "unknown/disconnected" -HAMLIB_FREQUENCY: int = 0 -HAMLIB_MODE: str = "" -HAMLIB_BANDWIDTH: int = 0 -HAMLIB_RF: int = 0 -HAMLIB_ALC: int = 0 -HAMLIB_STRENGTH: int = 0 -# ------------------------- -# FreeDV Defaults - -SNR: float = 0 -FREQ_OFFSET: float = 0 -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 -TX_DELAY: int = 0 # delay in ms before sending modulation for triggering VOX for example or slow PTT radios -# --------------------------------- - -# Audio Defaults -TX_AUDIO_LEVEL: int = 50 -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_AUTO_TUNE: bool = False -# Audio TCI Support -AUDIO_ENABLE_TCI: bool = False -TCI_IP: str = '127.0.0.1' -TCI_PORT: int = '9000' - -AUDIO_DBFS: int = 0 -FFT: list = [0] -ENABLE_FFT: bool = True -CHANNEL_BUSY: bool = False - -# ARQ PROTOCOL VERSION -# v.5 - signalling frame uses datac0 -# v.6 - signalling frame uses datac13 -# CHECKED # ARQ_PROTOCOL_VERSION: int = 6 - -# ARQ statistics -# CHECKED # SPEED_LIST: list = [] -# CHECKED # ARQ_BYTES_PER_MINUTE_BURST: int = 0 -# CHECKED # ARQ_BYTES_PER_MINUTE: int = 0 -# CHECKED # ARQ_BITS_PER_SECOND_BURST: int = 0 -# CHECKED # ARQ_BITS_PER_SECOND: int = 0 -# CHECKED # ARQ_COMPRESSION_FACTOR: int = 0 -# CHECKED # ARQ_TRANSMISSION_PERCENT: int = 0 -# CHECKED # ARQ_SECONDS_UNTIL_FINISH: int = 0 -# CHECKED # ARQ_SPEED_LEVEL: int = 0 -# CHECKED # TOTAL_BYTES: int = 0 -# set save to folder state for allowing downloading files to local file system -# CHECKED # ARQ_SAVE_TO_FOLDER: bool = False - -# CHANNEL_STATE = 'RECEIVING_SIGNALLING' -TNC_STATE: str = "IDLE" -# CHECKED # ARQ_STATE: bool = False -# CHECKED # ARQ_SESSION: bool = False -# disconnected, connecting, connected, disconnecting, failed -# CHECKED # ARQ_SESSION_STATE: str = "disconnected" - -# BEACON STATE -BEACON_STATE: bool = False -BEACON_PAUSE: bool = False - -# ------- RX BUFFER -# CHECKED # RX_MSG_BUFFER: list = [] -# CHECKED # RX_BURST_BUFFER: list = [] -# CHECKED # RX_FRAME_BUFFER: bytes = b"" -# CHECKED # RX_BUFFER_SIZE: int = 16 - -# ------- HEARD STATIONS BUFFER -HEARD_STATIONS: list = [] - -# ------- CODEC2 SETTINGS -TUNING_RANGE_FMIN: float = -50.0 -TUNING_RANGE_FMAX: float = 50.0 -IS_CODEC2_TRAFFIC: bool = False # true if we have codec2 signalling mode traffic on channel + TEST_FRAME = 255 \ No newline at end of file diff --git a/tnc/stats.py b/modem/stats.py similarity index 92% rename from tnc/stats.py rename to modem/stats.py index 4fb9731c..d295b9fa 100644 --- a/tnc/stats.py +++ b/modem/stats.py @@ -12,8 +12,7 @@ import threading import time import ujson as json import structlog -import static -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem log = structlog.get_logger("stats") @@ -48,7 +47,7 @@ class stats(): 'duration': duration, 'percentage': ARQ.arq_transmission_percent, 'status': status, - 'version': TNC.version + 'version': Modem.version } station_data = json.dumps(station_data) diff --git a/tnc/tci.py b/modem/tci.py similarity index 98% rename from tnc/tci.py rename to modem/tci.py index 2b459dc7..3d353f52 100644 --- a/tnc/tci.py +++ b/modem/tci.py @@ -7,7 +7,7 @@ import websocket import numpy as np import time from queues import AUDIO_TRANSMIT_QUEUE, AUDIO_RECEIVED_QUEUE -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem class TCICtrl: def __init__(self, hostname='127.0.0.1', port=50001): diff --git a/test/hamlib-test.py b/test/hamlib-test.py index 87ddb6b7..ad68f7d5 100644 --- a/test/hamlib-test.py +++ b/test/hamlib-test.py @@ -39,7 +39,7 @@ class RETCODE(Enum): RIG_EVFO = 16 RIG_EDOM = 17 -libname = pathlib.Path("../tnc/lib/hamlib/linux/libhamlib.so") +libname = pathlib.Path("../modem/lib/hamlib/linux/libhamlib.so") hamlib = ctypes.CDLL(libname) class SERIAL(ctypes.Structure): diff --git a/test/test_helpers.py b/test/test_helpers.py index 7fc8d3db..fb043c8b 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Unit test common helper routines used throughout the TNC. +Unit test common helper routines used throughout the Modem. Can be invoked from CMake, pytest, coverage or directly. @@ -14,7 +14,7 @@ import sys import helpers import pytest -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem @pytest.mark.parametrize("callsign", ["AA1AA", "DE2DE", "E4AWQ-4"]) diff --git a/test/test_highsnr_stdio_C_P_datacx.py b/test/test_highsnr_stdio_C_P_datacx.py index aa095cd6..231ff380 100644 --- a/test/test_highsnr_stdio_C_P_datacx.py +++ b/test/test_highsnr_stdio_C_P_datacx.py @@ -66,9 +66,9 @@ def t_HighSNR_C_P_DATACx( tx_side = "freedv_data_raw_tx" _txpaths = ( - os.path.join("..", "tnc") - if os.path.exists(os.path.join("..", "tnc")) - else "tnc" + os.path.join("..", "modem") + if os.path.exists(os.path.join("..", "modem")) + else "modem" ) _txpaths = glob.glob(rf"{_txpaths}/**/{tx_side}", recursive=True) for path in _txpaths: diff --git a/test/test_highsnr_stdio_P_C_datacx.py b/test/test_highsnr_stdio_P_C_datacx.py index 5574636d..81cde7a5 100644 --- a/test/test_highsnr_stdio_P_C_datacx.py +++ b/test/test_highsnr_stdio_P_C_datacx.py @@ -59,9 +59,9 @@ def t_HighSNR_P_C_DATACx(bursts: int, frames_per_burst: int, mode: str): # Facilitate running from main directory as well as inside test/ rx_side = "freedv_data_raw_rx" _rxpath = ( - os.path.join("..", "tnc") - if os.path.exists(os.path.join("..", "tnc")) - else "tnc" + os.path.join("..", "modem") + if os.path.exists(os.path.join("..", "modem")) + else "modem" ) _rxpaths = glob.glob(rf"{_rxpath}/**/{rx_side}", recursive=True) for path in _rxpaths: diff --git a/test/test_tnc.py b/test/test_modem.py similarity index 74% rename from test/test_tnc.py rename to test/test_modem.py index 58de47b1..ab81ae92 100755 --- a/test/test_tnc.py +++ b/test/test_modem.py @@ -4,12 +4,12 @@ Test connect frame commands over a high quality simulated audio channel. Near end-to-end test for sending / receiving connection control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. +Modem and modem and back through on the other station. Data injection initiates from the +queue used by the daemon process into and out of the Modem. Can be invoked from CMake, pytest, coverage or directly. -Uses util_tnc_I[RS]S.py in separate process to perform the data transfer. +Uses util_modem_I[RS]S.py in separate process to perform the data transfer. @author: DJ2LS, N2KIQ """ @@ -24,19 +24,19 @@ import pytest import structlog try: - import test.util_tnc_IRS as irs - import test.util_tnc_ISS as iss + import test.util_modem_IRS as irs + import test.util_modem_ISS as iss except ImportError: - import util_tnc_IRS as irs - import util_tnc_ISS as iss + import util_modem_IRS as irs + import util_modem_ISS as iss # This test is currently a little inconsistent. @pytest.mark.parametrize("command", ["CONNECT"]) @pytest.mark.flaky(reruns=2) -def test_tnc(command, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_tnc", level="INFO") - log = structlog.get_logger("test_tnc") +def test_modem(command, tmp_path): + log_handler.setup_logging(filename=tmp_path / "test_modem", level="INFO") + log = structlog.get_logger("test_modem") iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command, tmp_path]) irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command, tmp_path]) diff --git a/test/test_tnc_states.py b/test/test_modem_states.py similarity index 90% rename from test/test_tnc_states.py rename to test/test_modem_states.py index d4aab6dc..9fd0ae00 100644 --- a/test/test_tnc_states.py +++ b/test/test_modem_states.py @@ -4,8 +4,8 @@ Test control frame messages over a high quality simulated audio channel. Near end-to-end test for sending / receiving select control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. +Modem and modem and back through on the other station. Data injection initiates from the +queue used by the daemon process into and out of the Modem. Can be invoked from CMake, pytest, coverage or directly. @@ -23,10 +23,10 @@ import pytest # pylint: disable=wrong-import-position sys.path.insert(0, "..") -sys.path.insert(0, "../tnc") +sys.path.insert(0, "../modem") import data_handler import helpers -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem def print_frame(data: bytearray): @@ -161,23 +161,23 @@ def t_foreign_disconnect(mycall: str, dxcall: str): Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - # Create the TNC - tnc = data_handler.DATA() - tnc.arq_cleanup() + # Create the Modem + modem = data_handler.DATA() + modem.arq_cleanup() # Replace the heartbeat transmit routine with a No-Op. - tnc.transmit_session_heartbeat = t_tsh_dummy + modem.transmit_session_heartbeat = t_tsh_dummy # Create frame to be 'received' by this station. create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall) print_frame(create_frame) - tnc.received_session_opener(create_frame) + modem.received_session_opener(create_frame) assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes assert ARQ.arq_session is True - assert TNC.tnc_state == "BUSY" + assert Modem.modem_state == "BUSY" assert ARQ.arq_session_state == "connecting" # Set up a frame from a non-associated station. @@ -200,8 +200,8 @@ def t_foreign_disconnect(mycall: str, dxcall: str): # helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True # ), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal." - # Send the non-associated session close frame to the TNC - tnc.received_session_close(close_frame) + # Send the non-associated session close frame to the Modem + modem.received_session_close(close_frame) assert helpers.callsign_to_bytes(Station.mycallsign) == helpers.callsign_to_bytes( mycall @@ -211,7 +211,7 @@ def t_foreign_disconnect(mycall: str, dxcall: str): ), f"{Station.dxcallsign} != {dxcall} but should equal." assert ARQ.arq_session is True - assert TNC.tnc_state == "BUSY" + assert Modem.modem_state == "BUSY" assert ARQ.arq_session_state == "connecting" @@ -241,20 +241,20 @@ def t_valid_disconnect(mycall: str, dxcall: str): Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - # Create the TNC - tnc = data_handler.DATA() - tnc.arq_cleanup() + # Create the Modem + modem = data_handler.DATA() + modem.arq_cleanup() # Replace the heartbeat transmit routine with our own, a No-Op. - tnc.transmit_session_heartbeat = t_tsh_dummy + modem.transmit_session_heartbeat = t_tsh_dummy # Create packet to be 'received' by this station. create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall) print_frame(create_frame) - tnc.received_session_opener(create_frame) - + modem.received_session_opener(create_frame) + print(ARQ.arq_session) assert ARQ.arq_session is True - assert TNC.tnc_state == "BUSY" + assert Modem.modem_state == "BUSY" assert ARQ.arq_session_state == "connecting" # Create packet to be 'received' by this station. @@ -265,20 +265,20 @@ def t_valid_disconnect(mycall: str, dxcall: str): close_frame = t_create_session_close(open_session, mycall) print(close_frame[2:5]) print_frame(close_frame) - tnc.received_session_close(close_frame) + modem.received_session_close(close_frame) assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes assert ARQ.arq_session is False - assert TNC.tnc_state == "IDLE" + assert Modem.modem_state == "IDLE" assert ARQ.arq_session_state == "disconnected" # These tests are pushed into separate processes as a workaround. These tests # change the state of one of the static parts of the system. Unfortunately the # specific state(s) maintained across tests in the same interpreter are not yet known. -# The other tests affected are: `test_tnc.py` and the ARQ tests. +# The other tests affected are: `test_modem.py` and the ARQ tests. @pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"]) diff --git a/test/util_callback_multimode_rx.py b/test/util_callback_multimode_rx.py index 3ef2d35c..94eda986 100644 --- a/test/util_callback_multimode_rx.py +++ b/test/util_callback_multimode_rx.py @@ -16,7 +16,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_callback_multimode_rx_outside.py b/test/util_callback_multimode_rx_outside.py index eb4d8952..1bef5a51 100644 --- a/test/util_callback_multimode_rx_outside.py +++ b/test/util_callback_multimode_rx_outside.py @@ -15,7 +15,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_callback_multimode_tx.py b/test/util_callback_multimode_tx.py index 79e30284..c155c366 100644 --- a/test/util_callback_multimode_tx.py +++ b/test/util_callback_multimode_tx.py @@ -16,7 +16,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_callback_rx.py b/test/util_callback_rx.py index 0a7237b1..3c17860e 100644 --- a/test/util_callback_rx.py +++ b/test/util_callback_rx.py @@ -15,7 +15,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_callback_rx_outside.py b/test/util_callback_rx_outside.py index dc1fc3ce..c038c547 100644 --- a/test/util_callback_rx_outside.py +++ b/test/util_callback_rx_outside.py @@ -15,7 +15,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_callback_tx.py b/test/util_callback_tx.py index 3991a71f..ba3ace1d 100644 --- a/test/util_callback_tx.py +++ b/test/util_callback_tx.py @@ -17,7 +17,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 # --------------------------------------------GET PARAMETER INPUTS parser = argparse.ArgumentParser(description="FreeDATA audio test") diff --git a/test/util_chat_text_1.py b/test/util_chat_text_1.py index 444f5460..f9e9eae8 100644 --- a/test/util_chat_text_1.py +++ b/test/util_chat_text_1.py @@ -3,8 +3,8 @@ Send-side station emulator for connect frame tests over a high quality simulated audio channel. Near end-to-end test for sending / receiving connection control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. +Modem and modem and back through on the other station. Data injection initiates from the +queue used by the daemon process into and out of the Modem. Invoked from test_chat_text.py. @@ -22,7 +22,7 @@ import data_handler import helpers import modem import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog @@ -40,9 +40,9 @@ def t_setup( modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel2" HamlibParam.hamlib_radiocontrol = "disabled" - TNC.low_bandwidth_mode = lowbwmode + Modem.low_bandwidth_mode = lowbwmode Station.mygrid = bytes("AA12aa", "utf-8") - TNC.respond_to_cq = True + Modem.respond_to_cq = True Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # override ARQ SESSION STATE for allowing disconnect command ARQ.arq_session_state = "connected" @@ -57,17 +57,17 @@ def t_setup( Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - # Create the TNC - tnc = data_handler.DATA() + # Create the Modem + modem = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc.log = structlog.get_logger("station1_DATA") + modem.log = structlog.get_logger("station1_DATA") # Limit the frame-ack timeout - tnc.time_list_low_bw = [3, 1, 1] - tnc.time_list_high_bw = [3, 1, 1] - tnc.time_list = [3, 1, 1] + modem.time_list_low_bw = [3, 1, 1] + modem.time_list_high_bw = [3, 1, 1] + modem.time_list = [3, 1, 1] # Limit number of retries - tnc.rx_n_max_retries_per_burst = 5 + modem.rx_n_max_retries_per_burst = 5 # Create the modem t_modem = modem.RF() @@ -75,7 +75,7 @@ def t_setup( modem.RF.transmit = t_transmit t_modem.log = structlog.get_logger("station1_RF") - return tnc, orig_rx_func, orig_tx_func + return modem, orig_rx_func, orig_tx_func def t_highsnr_arq_short_station1( @@ -105,7 +105,7 @@ def t_highsnr_arq_short_station1( log.info("S1 TX: ", mode=static.FRAME_TYPE(frametype).name) if ( - TNC.low_bandwidth_mode + Modem.low_bandwidth_mode and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_W.value ): mesg = ( @@ -116,7 +116,7 @@ def t_highsnr_arq_short_station1( log.error(mesg) assert False, mesg if ( - not TNC.low_bandwidth_mode + not Modem.low_bandwidth_mode and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_N.value ): mesg = ( @@ -149,7 +149,7 @@ def t_highsnr_arq_short_station1( # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc, orig_rx_func, orig_tx_func = t_setup( + modem, orig_rx_func, orig_tx_func = t_setup( mycall, dxcall, lowbwmode, t_transmit, t_process_data, tmp_path ) @@ -171,7 +171,7 @@ def t_highsnr_arq_short_station1( ], } - sock.process_tnc_commands(json.dumps(data, indent=None)) + sock.process_modem_commands(json.dumps(data, indent=None)) # Assure the test completes. timeout = time.time() + 25 @@ -187,23 +187,23 @@ def t_highsnr_arq_short_station1( log.info("station1, first", arq_state=pformat(ARQ.arq_state)) data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.process_tnc_commands(json.dumps(data, indent=None)) + sock.process_modem_commands(json.dumps(data, indent=None)) time.sleep(0.5) # override ARQ SESSION STATE for allowing disconnect command ARQ.arq_session_state = "connected" - sock.process_tnc_commands(json.dumps(data, indent=None)) + sock.process_modem_commands(json.dumps(data, indent=None)) # Allow enough time for this side to process the disconnect frame. timeout = time.time() + 20 - while ARQ.arq_state or tnc.data_queue_transmit.queue: + while ARQ.arq_state or modem.data_queue_transmit.queue: if time.time() > timeout: log.error("station1", TIMEOUT=True) break time.sleep(0.5) log.info("station1", arq_state=pformat(ARQ.arq_state)) - # log.info("S1 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S1 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) + # log.info("S1 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) log.info("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) assert '"arq":"transmission","status":"transmitting"' in str( diff --git a/test/util_chat_text_2.py b/test/util_chat_text_2.py index 77f2523b..a9b8c5bf 100644 --- a/test/util_chat_text_2.py +++ b/test/util_chat_text_2.py @@ -3,8 +3,8 @@ Receive-side station emulator for connect frame tests over a high quality simulated audio channel. Near end-to-end test for sending / receiving connection control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. +Modem and modem and back through on the other station. Data injection initiates from the +queue used by the daemon process into and out of the Modem. Invoked from test_chat_text.py. @@ -19,7 +19,7 @@ import data_handler import helpers import modem import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog @@ -37,9 +37,9 @@ def t_setup( modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel1" HamlibParam.hamlib_radiocontrol = "disabled" - TNC.low_bandwidth_mode = lowbwmode + Modem.low_bandwidth_mode = lowbwmode Station.mygrid = bytes("AA12aa", "utf-8") - TNC.respond_to_cq = True + Modem.respond_to_cq = True Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # override ARQ SESSION STATE for allowing disconnect command ARQ.arq_session_state = "connected" @@ -54,17 +54,17 @@ def t_setup( Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - # Create the TNC - tnc = data_handler.DATA() + # Create the Modem + modem = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc.log = structlog.get_logger("station2_DATA") + modem.log = structlog.get_logger("station2_DATA") # Limit the frame-ack timeout - tnc.time_list_low_bw = [1, 1, 1] - tnc.time_list_high_bw = [1, 1, 1] - tnc.time_list = [1, 1, 1] + modem.time_list_low_bw = [1, 1, 1] + modem.time_list_high_bw = [1, 1, 1] + modem.time_list = [1, 1, 1] # Limit number of retries - tnc.rx_n_max_retries_per_burst = 5 + modem.rx_n_max_retries_per_burst = 5 # Create the modem t_modem = modem.RF() @@ -72,7 +72,7 @@ def t_setup( modem.RF.transmit = t_transmit t_modem.log = structlog.get_logger("station2_RF") - return tnc, orig_rx_func, orig_tx_func + return modem, orig_rx_func, orig_tx_func def t_highsnr_arq_short_station2( @@ -123,7 +123,7 @@ def t_highsnr_arq_short_station2( # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc, orig_rx_func, orig_tx_func = t_setup( + modem, orig_rx_func, orig_tx_func = t_setup( mycall, dxcall, lowbwmode, t_transmit, t_process_data, tmp_path ) @@ -153,8 +153,8 @@ def t_highsnr_arq_short_station2( time.sleep(0.5) log.info("station2", arq_state=pformat(ARQ.arq_state)) - # log.info("S2 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S2 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) + # log.info("S2 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) log.info("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) assert '"arq":"transmission","status":"received"' in str(sock.SOCKET_QUEUE.queue) diff --git a/test/util_datac13.py b/test/util_datac13.py index 4c3c9833..6dcc0b65 100644 --- a/test/util_datac13.py +++ b/test/util_datac13.py @@ -3,9 +3,9 @@ Send- and receive-side station emulator for control frame tests over a high quality simulated audio channel. -Near end-to-end test for sending / receiving control frames through the TNC and modem +Near end-to-end test for sending / receiving control frames through the Modem and modem and back through on the other station. Data injection initiates from the queue used -by the daemon process into and out of the TNCParam. +by the daemon process into and out of the ModemParam. Invoked from test_datac13.py. @@ -21,7 +21,7 @@ import data_handler import helpers import modem import sock -from static import ARQ, HamlibParam, ModemParam, Station, TNC as TNCParam +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem from static import FRAME_TYPE as FR_TYPE import structlog #from static import FRAME_TYPE as FR_TYPE @@ -48,9 +48,9 @@ def t_setup( modem.TESTMODE = True modem.TXCHANNEL = tmp_path / tx_channel HamlibParam.hamlib_radiocontrol = "disabled" - TNCParam.low_bandwidth_mode = lowbwmode or True + ModemParam.low_bandwidth_mode = lowbwmode or True Station.mygrid = bytes("AA12aa", "utf-8") - TNCParam.respond_to_cq = True + ModemParam.respond_to_cq = True Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] mycallsign = helpers.callsign_to_bytes(mycall) @@ -63,17 +63,17 @@ def t_setup( Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - # Create the TNC - tnc_data_handler = data_handler.DATA() + # Create the Modem + modem_data_handler = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc_data_handler.log = structlog.get_logger(f"station{station}_DATA") + modem_data_handler.log = structlog.get_logger(f"station{station}_DATA") # Limit the frame-ack timeout - tnc_data_handler.time_list_low_bw = [8, 8, 8] - tnc_data_handler.time_list_high_bw = [8, 8, 8] - tnc_data_handler.time_list = [8, 8, 8] + modem_data_handler.time_list_low_bw = [8, 8, 8] + modem_data_handler.time_list_high_bw = [8, 8, 8] + modem_data_handler.time_list = [8, 8, 8] # Limit number of retries - tnc_data_handler.rx_n_max_retries_per_burst = 4 + modem_data_handler.rx_n_max_retries_per_burst = 4 ModemParam.tx_delay = 50 # add additional delay time for passing test @@ -83,7 +83,7 @@ def t_setup( modem.RF.transmit = t_transmit t_modem.log = structlog.get_logger(f"station{station}_RF") - return tnc_data_handler, orig_rx_func, orig_tx_func + return modem_data_handler, orig_rx_func, orig_tx_func def t_datac13_1( @@ -134,7 +134,7 @@ def t_datac13_1( # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc_data_handler, orig_rx_func, orig_tx_func = t_setup( + modem_data_handler, orig_rx_func, orig_tx_func = t_setup( 1, mycall, dxcall, @@ -151,12 +151,12 @@ def t_datac13_1( time.sleep(0.5) if "stop" in data["command"]: - log.debug("t_datac13_1: STOP test, setting TNC state") - TNCParam.tnc_state = "BUSY" + log.debug("t_datac13_1: STOP test, setting Modem state") + ModemParam.modem_state = "BUSY" ARQ.arq_state = True - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) time.sleep(0.5) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) # Assure the test completes. timeout = time.time() + timeout_duration + 5 @@ -174,20 +174,20 @@ def t_datac13_1( # override ARQ SESSION STATE for allowing disconnect command ARQ.arq_session_state = "connected" data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) time.sleep(0.5) # Allow enough time for this side to process the disconnect frame. timeout = time.time() + timeout_duration - while tnc_data_handler.data_queue_transmit.queue: + while modem_data_handler.data_queue_transmit.queue: if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=tnc_data_handler.data_queue_transmit.queue) + log.warning("station1", TIMEOUT=True, dq_tx=modem_data_handler.data_queue_transmit.queue) break time.sleep(0.5) log.info("station1, final") - # log.info("S1 DQT: ", DQ_Tx=pformat(TNCParam.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(TNCParam.data_queue_received.queue)) + # log.info("S1 DQT: ", DQ_Tx=pformat(ModemParam.data_queue_transmit.queue)) + # log.info("S1 DQR: ", DQ_Rx=pformat(ModemParam.data_queue_received.queue)) log.debug("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_tx_check: @@ -265,11 +265,11 @@ def t_datac13_2( log.info("t_datac13_2:", RXCHANNEL=modem.RXCHANNEL) log.info("t_datac13_2:", TXCHANNEL=modem.TXCHANNEL) - # TODO: Why do we need this when calling CQ? + # TODO Why do we need this when calling CQ? #if "cq" in data: # t_data = {"type": "arq", "command": "stop_transmission"} - # sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None)) - # sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None)) + # sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) + # sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) # Assure the test completes. timeout = time.time() + timeout_duration @@ -296,14 +296,14 @@ def t_datac13_2( time.sleep(0.5) log.info("station2, final") - # log.info("S2 DQT: ", DQ_Tx=pformat(TNCParam.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(TNCParam.data_queue_received.queue)) + # log.info("S2 DQT: ", DQ_Tx=pformat(ModemParam.data_queue_transmit.queue)) + # log.info("S2 DQR: ", DQ_Rx=pformat(ModemParam.data_queue_received.queue)) log.debug("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_rx_check: assert item in str( sock.SOCKET_QUEUE.queue ), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}" - # TODO: Not sure why we need this for every test run + # 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!") diff --git a/test/util_datac13_negative.py b/test/util_datac13_negative.py index 882acc98..f09e948e 100644 --- a/test/util_datac13_negative.py +++ b/test/util_datac13_negative.py @@ -15,7 +15,7 @@ import data_handler import helpers import modem import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, FRAME_TYPE as FR_TYPE +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog #from static import FRAME_TYPE as FR_TYPE @@ -41,9 +41,9 @@ def t_setup( modem.TESTMODE = True modem.TXCHANNEL = tmp_path / tx_channel HamlibParam.hamlib_radiocontrol = "disabled" - TNC.low_bandwidth_mode = lowbwmode or True + Modem.low_bandwidth_mode = lowbwmode or True Station.mygrid = bytes("AA12aa", "utf-8") - TNC.respond_to_cq = True + Modem.respond_to_cq = True Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] mycallsign_bytes = helpers.callsign_to_bytes(mycall) @@ -56,17 +56,17 @@ def t_setup( Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - # Create the TNC - tnc_data_handler = data_handler.DATA() + # Create the Modem + modem_data_handler = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc_data_handler.log = structlog.get_logger(f"station{station}_DATA") + modem_data_handler.log = structlog.get_logger(f"station{station}_DATA") # Limit the frame-ack timeout - tnc_data_handler.time_list_low_bw = [8, 8, 8] - tnc_data_handler.time_list_high_bw = [8, 8, 8] - tnc_data_handler.time_list = [8, 8, 8] + modem_data_handler.time_list_low_bw = [8, 8, 8] + modem_data_handler.time_list_high_bw = [8, 8, 8] + modem_data_handler.time_list = [8, 8, 8] # Limit number of retries - tnc_data_handler.rx_n_max_retries_per_burst = 4 + modem_data_handler.rx_n_max_retries_per_burst = 4 ModemParam.tx_delay = 50 # add additional delay time for passing test # Create the modem t_modem = modem.RF() @@ -74,7 +74,7 @@ def t_setup( modem.RF.transmit = t_transmit t_modem.log = structlog.get_logger(f"station{station}_RF") - return tnc_data_handler, orig_rx_func, orig_tx_func + return modem_data_handler, orig_rx_func, orig_tx_func def t_datac13_1( @@ -125,7 +125,7 @@ def t_datac13_1( # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc_data_handler, orig_rx_func, orig_tx_func = t_setup( + modem_data_handler, orig_rx_func, orig_tx_func = t_setup( 1, mycall, dxcall, @@ -144,16 +144,16 @@ def t_datac13_1( if "stop" in data["command"]: time.sleep(0.5) log.debug( - "t_datac13_1: STOP test, setting TNC state", + "t_datac13_1: STOP test, setting Modem state", mycall=Station.mycallsign, dxcall=Station.dxcallsign, ) Station.dxcallsign = helpers.callsign_to_bytes(data["dxcallsign"]) Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) - TNC.tnc_state = "BUSY" + Modem.modem_state = "BUSY" ARQ.arq_state = True - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) # Assure the test completes. timeout = time.time() + timeout_duration @@ -177,20 +177,20 @@ def t_datac13_1( # override ARQ SESSION STATE for allowing disconnect command ARQ.arq_session_state = "connected" data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) time.sleep(0.5) # Allow enough time for this side to process the disconnect frame. timeout = time.time() + timeout_duration - while tnc_data_handler.data_queue_transmit.queue: + while modem_data_handler.data_queue_transmit.queue: if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=tnc_data_handler.data_queue_transmit.queue) + log.warning("station1", TIMEOUT=True, dq_tx=modem_data_handler.data_queue_transmit.queue) break time.sleep(0.5) log.info("station1, final") - # log.info("S1 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S1 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) + # log.info("S1 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) log.debug("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_tx_check: @@ -270,8 +270,8 @@ def t_datac13_2( if "cq" in data: t_data = {"type": "arq", "command": "stop_transmission"} - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None)) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) # Assure the test completes. timeout = time.time() + timeout_duration @@ -298,14 +298,14 @@ def t_datac13_2( time.sleep(0.5) log.info("station2, final") - # log.info("S2 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S2 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) + # log.info("S2 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) log.debug("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_rx_check: assert item not in str( sock.SOCKET_QUEUE.queue ), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}" - # TODO: Not sure why we need this for every test run + # 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!") diff --git a/test/util_tnc_IRS.py b/test/util_modem_IRS.py similarity index 71% rename from test/util_tnc_IRS.py rename to test/util_modem_IRS.py index 99507cd3..cb865ebf 100644 --- a/test/util_tnc_IRS.py +++ b/test/util_modem_IRS.py @@ -6,7 +6,7 @@ Near end-to-end test for sending / receiving connection control frames through t TNC and modem and back through on the other station. Data injection initiates from the queue used by the daemon process into and out of the TNC. -Invoked from test_tnc.py. +Invoked from test_modem.py. @author: N2KIQ """ @@ -18,17 +18,17 @@ from typing import Callable import structlog -sys.path.insert(0, "../tnc") +sys.path.insert(0, "../modem") import data_handler import helpers import modem import sock -import static +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem IRS_original_arq_cleanup: Callable MESSAGE: str -log = structlog.get_logger("util_tnc_IRS") +log = structlog.get_logger("util_modem_IRS") def irs_arq_cleanup(): @@ -52,42 +52,45 @@ def irs_arq_cleanup(): def t_arq_irs(*args): + # not sure why importing at top level isn't working + import modem + import data_handler # pylint: disable=global-statement global IRS_original_arq_cleanup, MESSAGE MESSAGE = args[0] tmp_path = args[1] - sock.log = structlog.get_logger("util_tnc_IRS_sock") + sock.log = structlog.get_logger("util_modem_IRS_sock") # enable testmode data_handler.TESTMODE = True modem.RXCHANNEL = tmp_path / "hfchannel2" modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel1" - static.HAMLIB_RADIOCONTROL = "disabled" - static.RESPOND_TO_CQ = True + HamlibParam.hamlib_radiocontrol = "disabled" + Modem.respond_to_cq = True log.info("t_arq_irs:", RXCHANNEL=modem.RXCHANNEL) log.info("t_arq_irs:", TXCHANNEL=modem.TXCHANNEL) mycallsign = bytes("DN2LS-2", "utf-8") mycallsign = helpers.callsign_to_bytes(mycallsign) - static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) - static.MYGRID = bytes("AA12aa", "utf-8") - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + Station.mycallsign = helpers.bytes_to_callsign(mycallsign) + Station.mycallsign_CRC = helpers.get_crc_24(Station.mycallsign) + Station.mygrid = bytes("AA12aa", "utf-8") + Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # start data handler - tnc = data_handler.DATA() - tnc.log = structlog.get_logger("util_tnc_IRS_DATA") + data_handler = data_handler.DATA() + data_handler.log = structlog.get_logger("util_modem_IRS_DATA") # Inject a way to exit the TNC infinite loop - IRS_original_arq_cleanup = tnc.arq_cleanup - tnc.arq_cleanup = irs_arq_cleanup + IRS_original_arq_cleanup = data_handler.arq_cleanup + data_handler.arq_cleanup = irs_arq_cleanup # start modem t_modem = modem.RF() - t_modem.log = structlog.get_logger("util_tnc_IRS_RF") + t_modem.log = structlog.get_logger("util_modem_IRS_RF") # Set timeout timeout = time.time() + 15 diff --git a/test/util_tnc_ISS.py b/test/util_modem_ISS.py similarity index 74% rename from test/util_tnc_ISS.py rename to test/util_modem_ISS.py index f1a69f19..1d06203f 100644 --- a/test/util_tnc_ISS.py +++ b/test/util_modem_ISS.py @@ -6,7 +6,7 @@ Near end-to-end test for sending / receiving connection control frames through t TNC and modem and back through on the other station. Data injection initiates from the queue used by the daemon process into and out of the TNC. -Invoked from test_tnc.py. +Invoked from test_modem.py. @author: DJ2LS, N2KIQ """ @@ -19,17 +19,18 @@ from typing import Callable import structlog -sys.path.insert(0, "../tnc") +sys.path.insert(0, "../modem") import data_handler import helpers import modem import sock import static +from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem ISS_original_arq_cleanup: Callable MESSAGE: str -log = structlog.get_logger("util_tnc_ISS") +log = structlog.get_logger("util_modem_ISS") def iss_arq_cleanup(): @@ -53,49 +54,52 @@ def iss_arq_cleanup(): def t_arq_iss(*args): + # not sure why importing at top level isn't working + import modem + import data_handler # pylint: disable=global-statement global ISS_original_arq_cleanup, MESSAGE MESSAGE = args[0] tmp_path = args[1] - sock.log = structlog.get_logger("util_tnc_ISS_sock") + sock.log = structlog.get_logger("util_modem_ISS_sock") # enable testmode data_handler.TESTMODE = True modem.RXCHANNEL = tmp_path / "hfchannel1" modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel2" - static.HAMLIB_RADIOCONTROL = "disabled" + HamlibParam.hamlib_radiocontrol = "disabled" log.info("t_arq_iss:", RXCHANNEL=modem.RXCHANNEL) log.info("t_arq_iss:", TXCHANNEL=modem.TXCHANNEL) mycallsign = bytes("DJ2LS-2", "utf-8") mycallsign = helpers.callsign_to_bytes(mycallsign) - static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) - static.MYGRID = bytes("AA12aa", "utf-8") - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + Station.mycallsign = helpers.bytes_to_callsign(mycallsign) + Station.mycallsign_CRC = helpers.get_crc_24(Station.mycallsign) + Station.mygrid = bytes("AA12aa", "utf-8") + Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] dxcallsign = b"DN2LS-0" dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}' # start data handler - tnc = data_handler.DATA() - tnc.log = structlog.get_logger("util_tnc_ISS_DATA") + data_handler = data_handler.DATA() + data_handler.log = structlog.get_logger("util_modem_ISS_DATA") # Inject a way to exit the TNC infinite loop - ISS_original_arq_cleanup = tnc.arq_cleanup - tnc.arq_cleanup = iss_arq_cleanup + ISS_original_arq_cleanup = data_handler.arq_cleanup + data_handler.arq_cleanup = iss_arq_cleanup # start modem t_modem = modem.RF() - t_modem.log = structlog.get_logger("util_tnc_ISS_RF") + t_modem.log = structlog.get_logger("util_modem_ISS_RF") # mode = codec2.freedv_get_mode_value_by_name(FREEDV_MODE) # n_frames_per_burst = N_FRAMES_PER_BURST @@ -124,17 +128,17 @@ def t_arq_iss(*args): time.sleep(2.5) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) time.sleep(7.5) data = {"type": "arq", "command": "stop_transmission"} - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) time.sleep(2.5) - sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) + sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) # Set timeout timeout = time.time() + 15 diff --git a/test/util_multimode_rx.py b/test/util_multimode_rx.py index 5e59431f..aef517fc 100755 --- a/test/util_multimode_rx.py +++ b/test/util_multimode_rx.py @@ -23,7 +23,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 def test_mm_rx(): diff --git a/test/util_multimode_tx.py b/test/util_multimode_tx.py index 00d99b66..f9f5338b 100644 --- a/test/util_multimode_tx.py +++ b/test/util_multimode_tx.py @@ -22,7 +22,7 @@ import numpy as np import pyaudio sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 def test_mm_tx(): diff --git a/test/util_rx.py b/test/util_rx.py index 620c256a..127ef4bf 100644 --- a/test/util_rx.py +++ b/test/util_rx.py @@ -23,8 +23,8 @@ import sounddevice as sd # pylint: disable=wrong-import-position sys.path.insert(0, "..") -sys.path.insert(0, "../tnc") -from tnc import codec2 +sys.path.insert(0, "../modem") +from modem import codec2 def util_rx(): diff --git a/test/util_tx.py b/test/util_tx.py index c4cc6ec1..3c0e6d40 100644 --- a/test/util_tx.py +++ b/test/util_tx.py @@ -21,7 +21,7 @@ import numpy as np import sounddevice as sd sys.path.insert(0, "..") -from tnc import codec2 +from modem import codec2 def util_tx(): diff --git a/tnc/config.ini b/tnc/config.ini deleted file mode 100644 index 0789266c..00000000 --- a/tnc/config.ini +++ /dev/null @@ -1,41 +0,0 @@ -[NETWORK] -#network settings -tncport = 3000 - -[STATION] -#station settings -mycall = DN2LS-0 -mygrid = JN48cs -ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -[AUDIO] -#audio settings -rx = 6 -tx = 6 -txaudiolevel = 250 -auto_tune = False - -[RADIO] -#radio settings -radiocontrol = tci -rigctld_ip = 127.0.0.1 -rigctld_port = 4532 - -[TNC] -#tnc settings -scatter = True -fft = True -narrowband = False -fmin = -150.0 -fmax = 150.0 -qrv = True -rx_buffer_size = 16 -explorer = False -stats = False -fsk = False -tx_delay = 50 - -[TCI] -ip = 127.0.0.1 -port = 50001 - diff --git a/tools/Windows/GUI-Install-Requirements.bat b/tools/Windows/GUI-Install-Requirements.bat new file mode 100644 index 00000000..ecd0c113 --- /dev/null +++ b/tools/Windows/GUI-Install-Requirements.bat @@ -0,0 +1,5 @@ +REM Place this batch file in FreeData/tnc and then run it +REM ie. c:\FD-Src\gui_vue + +call npm install +pause \ No newline at end of file diff --git a/tools/Windows/GUI-Launch.bat b/tools/Windows/GUI-Launch.bat new file mode 100644 index 00000000..8b82932d --- /dev/null +++ b/tools/Windows/GUI-Launch.bat @@ -0,0 +1,5 @@ +REM Place this batch file in FreeData/tnc and then run it +REM ie. c:\FD-Src\gui_vue + +call npm start +pause \ No newline at end of file diff --git a/tools/Windows/GUI-Update-Requirements.bat b/tools/Windows/GUI-Update-Requirements.bat new file mode 100644 index 00000000..510b180e --- /dev/null +++ b/tools/Windows/GUI-Update-Requirements.bat @@ -0,0 +1,5 @@ +REM Place this batch file in FreeData/tnc and then run it +REM ie. c:\FD-Src\gui_vue + +call npm update +pause \ No newline at end of file diff --git a/tools/Windows/TNC-Install-Requrements.bat b/tools/Windows/TNC-Install-Requrements.bat new file mode 100644 index 00000000..bdbe4e6b --- /dev/null +++ b/tools/Windows/TNC-Install-Requrements.bat @@ -0,0 +1,5 @@ +REM Place this batch file in FreeData/tnc and then run it +REM ie. c:\FD-Src\tnc + +python -m pip install -r ..\requirements.txt +pause \ No newline at end of file diff --git a/tools/Windows/TNC-Launch.bat b/tools/Windows/TNC-Launch.bat new file mode 100644 index 00000000..8ac7af61 --- /dev/null +++ b/tools/Windows/TNC-Launch.bat @@ -0,0 +1,5 @@ +REM Place this batch file in FreeData/tnc and then run it +REM ie. c:\FD-Src\tnc + +python daemon.py +pause \ No newline at end of file diff --git a/tools/Windows/copy-files.bat b/tools/Windows/copy-files.bat new file mode 100644 index 00000000..02ecffb9 --- /dev/null +++ b/tools/Windows/copy-files.bat @@ -0,0 +1,6 @@ +REM This will copy the helper batch files to the approriate places for you + +copy GUI* ..\..\gui_vue\ +copy TNC* ..\..\tnc\ + +pause \ No newline at end of file diff --git a/tools/create_hmac_tokes.py b/tools/create_hmac_tokes.py index f7c6bc71..b66e5149 100755 --- a/tools/create_hmac_tokes.py +++ b/tools/create_hmac_tokes.py @@ -39,7 +39,7 @@ def create_hmac_salts(dxcallsign: str, mycallsign: str, num_tokens: int): for _ in range(len(token_array)): file.write(token_array[_] + '\n') - print("files created - place them in tnc/hmac folder and share the file with the remote station") + print("files created - place them in modem/hmac folder and share the file with the remote station") except Exception: print("error creating hmac file") diff --git a/tools/freedata_network_listener.py b/tools/freedata_network_listener.py index 651ccfeb..6a813038 100755 --- a/tools/freedata_network_listener.py +++ b/tools/freedata_network_listener.py @@ -5,7 +5,7 @@ daemon.py Author: DJ2LS, January 2022 -daemon for providing basic information for the tnc like audio or serial devices +daemon for providing basic information for the modem like audio or serial devices """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member @@ -92,7 +92,7 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: #pass print(jsondata.get("routing_table")) - if jsondata.get('freedata') == "tnc-message": + if jsondata.get('freedata') == "modem-message": log.info(jsondata) if jsondata.get('ping') == "acknowledge":